Create AppVolume Writables for AD Groups with PowerShell

If you wish to automate the Creation of AppVolume Writables for AD Groups with PowerShell, then look no further as this blogpost is exactly what you need.

Post has been updated with code fixes after multiple runs of Trials and Errors. Enjoy !

[CmdletBinding()]
    Param(

        [Parameter(Mandatory=$True)]
        [string]$AppVolumesUser,

        [Parameter(Mandatory=$True)]
        [securestring]$AppVolumesPassword

)

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AppVolumesPassword)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$AppVolumesDomain = $env:USERDOMAIN

$loginParams = @{
  username = "$AppVolumesDomain\$appvolumesuser"
  password = $Password
}

#=====================================
# Functions 
#=====================================

Function GetNiceDateTime {get-date -format "ddMMMyyy HH:mm:ss.fff"}


Function Login-Manager($uri,$username,$password)
{
    Write-Host -NoNewline "Logging in to manager..."
    $sessionUri = $uri + "/cv_api/sessions"
    $result = Invoke-WebRequest $sessionUri -Method POST -Body $loginParams -SessionVariable sessionStore -UseBasicParsing
	$success = Verify-Result $result "logging in"
	return $sessionStore
}

Function Get-Datastores($uri)
{
    $getAssignmentsUri = $uri + "/cv_api/datastores"
	$rawDatastores = Invoke-APIWithRetries $uri $getAssignmentsUri $null "GET" "Getting the list of datastores..." "getting all datastores"
    if ($rawDatastores)
	{
		$inputDatastores = ConvertFrom-Json $rawDatastores.Content
		$datastores = @{}
		foreach ($inputDatastore in $inputDatastores.datastores)
		{
			$datastores[$inputDatastore.uniq_string] = New-Object -TypeName PSObject |
			Add-Member -Name Name -Value $inputDatastore.name -MemberType NoteProperty -PassThru |
			Add-Member -Name Datacenter -Value $inputDatastore.datacenter -MemberType NoteProperty -PassThru |
            Add-Member -Name UniqString -Value $inputDatastore.uniq_string -MemberType NoteProperty -PassThru
		}
        Write-Host "Found"$datastores.Count"datastores in appvolumes"
        return $datastores
	}
    Write-Host -ForegroundColor Red "ERROR: Could not get datastore list"
}

Function Import-Volumes($uri,$writableDatacenter,$writableDatastore,$writablePath,$importWritables)
{
    $message = "Importing writables from " + $writableDatastore + "..."

    $importUri = $uri + "/cv_api/volumes/import"
    $importParams = @{datacenter=$writableDatacenter;datastore=$writableDatastore;path=$writablePath;delay="false";writable=$importWritables}
	
	$result = Invoke-APIWithRetries $uri $importUri $importParams "POST" $message "importing writables"
}

Function Invoke-APIWithRetries($cleanuri, $uri, $params, $verb, $message, $operationName)
{
	Write-Host -NoNewline $message
	$result = Invoke-API $uri $verb $params
	if (IsError-NotAuthenticated)
	{
		Set-Variable -Name sessionStore -Value (Login-Manager $cleanuri) -Scope Global
		Write-Host -NoNewline "Retry: "$message
		$result = Invoke-API $uri $verb $params
		$Error.Clear()
	}
	for ($retries = 1; $retries -le $retryCount;$retries++)
	{
		if ((Verify-Result $result $operationName) -eq $true) 
		{
			return $result
		}
		else
		{
			Write-Host -NoNewline "Retry "$retries":"$message
			$result = Invoke-API $uri $verb $params
		}
	}
	Write-Host "Giving up"$operationName" after "$retryCount" retries..."
	return $null
}

Function Invoke-API($uri, $verb, $params,$sessionstore)
{
	if ($params -eq $null)
		{return Invoke-WebRequest $uri -Method $verb -WebSession $sessionStore -UseBasicParsing}
	else
		{return Invoke-WebRequest $uri -Method $verb -Body $params -WebSession $sessionStore -UseBasicParsing}
}

Function IsError-NotAuthenticated()
{
	if ($Error.Count -gt 0)
	{
		if ($Error[0].tostring().contains("Forbidden. You must first log in.") -or 
			$Error[0].tostring().contains("Session expired. Please login again.") -or
			$Error[0].tostring().contains("Inauthentic request"))
		{
			$Error.Clear()
			return $true;
		}
	}
	return $false
}

Function Verify-Result($result, $operation)
{
    if ($result.StatusCode -ne 200)
    {
        Write-Host "Failed"$operation
        return $false
    }	
	Notify-Success $operation	
	return $true
}

Function Notify-Success($operation)
{
	switch ($operation)
	{
		"creating assignment" {
			if ($result.Content.Contains("All entities were already Assigned"))
				{Write-Host "Ok (Was already attached)"}
			else
				{Write-Host "Ok"}
		}
		default {Write-Host "Ok"}
	}
}

############ end functions


#=====================================
# Variables 
#=====================================

# derive some values from EUP_variable reg key used to configure appvols DB
# comment the try catch loopout and create variables manually if not running on appvols mgmt server.
#
 try  {"$(GetNiceDateTime) "+"Reading EUP_Appvolumesdatabase reg key to determine Datacentre / Pod."
       $appvolsreg = Get-ItemProperty HKLM:\SOFTWARE\ABCCORP\MachineConfigData\Data\ -Name EUP_appvolumesdatabase
       $appvolsdb = $appvolsreg.eup_appvolumesdatabase
       $appvolsdbarr = $appvolsdb.split("_")
       $appvolsdb =$appvolsdbarr[1]
       $datacentre = $appvolsdb.substring(0,4)
       "$(GetNiceDateTime) "+" Datacentre set to "+$datacentre 
       $pod = $appvolsdb.Substring($appvolsdb.Length-1)
       "$(GetNiceDateTime) "+" Pod set to POD"+$pod
        }
 catch{"$(GetNiceDateTime) Error reading Datacentre/ pod information from registry "+$_.Exception.Message
    Exit 
        }
switch ($datacentre.toUpper())
{
    'HACL' {$suffix = ".ldn.ABCCORP.com"} 
    'LO2X' {$suffix = ".ldn.ABCCORP.com"} 
    'SH90' {$suffix = ".stm.ABCCORP.com"} 
    'AS44' {$suffix = ".stm.ABCCORP.com"}
    'FFFC' {$suffix = ".zur.ABCCORP.com"}
    'VFVB' {$suffix = ".zur.ABCCORP.com"}
    'RZUD' {$suffix = ".zur.ABCCORP.com"}
    'DF1C' {$suffix = ".sng.ABCCORP.com"}
    'SI38' {$suffix = ".sng.ABCCORP.com"}
    'HOPA' {$suffix = ".hkg.ABCCORP.com"}
    'HONT' {$suffix = ".hkg.ABCCORP.com"}
    'TO6X' {$suffix = ".tky.ABCCORP.com"}
    'TOTK' {$suffix = ".tky.ABCCORP.com"}
    'SYLE' {$suffix = ".syd.ABCCORP.com"}
    'DF4E' {$suffix = ".syd.ABCCORP.com"}
    
    Default {"$(GetNiceDateTime) could not set suffix for datacentre "+$datacentre
    Exit
    }
}
$debug = $true #set $debug = false to run disable commands, otherwise report only
$McShortName=$env:COMPUTERNAME
$Mcdomain = (Get-WmiObject -Class Win32_ComputerSystem | Select-Object Domain).Domain.ToLower()
$mcshortdomain = $env:USERDOMAIN 


if ($mcshortdomain.ToUpper() -eq "ABCPROD") {
    $managerUri = "http://appvol-$datacentre-pod"+$pod+$suffix
}
else{
    $managerUri = "http://$mcshortdomain-appvol-$datacentre-pod"+$pod+$suffix
    }

#$managerUri = "http://localhost" #use when VIP not working

# $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition

$serveradobj = Get-ADComputer -identity $env:COMPUTERNAME
$location = $serveradobj.DistinguishedName.Split(",")[3]
$location = $location.split("=")[1]
$GroupName = 'App_Global_VM_A3_ABCCORP_L_WRT_A'
$GroupADObject = Get-ADGroup $GroupName
$GroupDN = $GroupADObject.DistinguishedName

# Replace "=" with "%3D" Replace "," with "%2C" and replace " " with "+"
$GroupDNEncoded = (($GroupDN.replace("=","%3D")).Replace(",","%2C").replace(" ","+"))


#=============
#SSL fixup
#=============

add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols


#=====================================
# Script execution 
#=====================================

#Retrieve list of available Datastores from management server and Populate others Variables
#connect
Set-Variable -Name sessStore -Value (Login-Manager -uri $managerUri -username "$AppVolumesDomain\$appvolumesuser" -password $Password) -Scope Global 
$getalldatastorescmd = "$managerUri/cv_api/datastores"
#$getalldatastorescmd = $managerUri +"/cv_api/datastores"
$datastoresraw = Invoke-API -uri $getalldatastorescmd -verb "GET" -params $null -sessionstore $sessStore
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions")        
$jsonserial= New-Object -TypeName System.Web.Script.Serialization.JavaScriptSerializer 
$jsonserial.MaxJsonLength  = 67108864
$datastores = $jsonserial.DeserializeObject($datastoresraw)
$datastore_uniq_string = $datastores['writable_storage']

# Replace "|" with "%7C"
$uniqStrEncoded = $datastore_uniq_string.Replace("|","%7C")
$datastorecollection = $datastores.datastores

$all = $datastorecollection.ForEach({$_.ForEach({[PSCustomObject]$_})})

$targetDS = $all | Where-Object {$_.Values -like "$datastore_uniq_string"}

# $test = $datastorecollection | Where-Object {$_.Values -like "$datastore_uniq_string"} | % ({$_.ForEach({[PSCustomObject]$_})}) # Single line for above 2 lines

$DatastoreName = $targetDS['name']
$DatastoreDisplayName = $targetDS['display_name']

Write-output "Datastore Unique String is $datastore_uniq_string"
Write-output "Datastore Name is $DatastoreName"
Write-output "Datastore Display_Name is $DatastoreDisplayName"
Write-output "Data Center is $datacentre"
Write-output "Data Center POD is POD$pod"

#CreateWritableVolumes


$createWVcmd = "$managerUri/cv_api/writables?action=create&bg=1&block_login=0&create_for%5B0%5D%5Bentity_type%5D=Group&create_for%5B0%5D%5Bname%5D=$GroupName&create_for%5B0%5D%5Bpath%5D=$GroupDNEncoded&datastore=$DatastoreName&defer_create=1&enable_version=0&path=cloudvolumes%2Fwritable&template_name=template_uia_only_80.vmdk&template_path=cloudvolumes%2Fwritable_templates&uniq_string=$uniqStrEncoded"
Invoke-API -uri $createWVcmd -verb "POST" -params $null -sessionstore $sessStore

4 thoughts on “Create AppVolume Writables for AD Groups with PowerShell

  1. The string should be URL encoded:
    Unencoded:
    create&bg=1&block_login=1&create_for[0][entity_type]=Group&create_for[0][name]=$GroupName&create_for[0][path]=$GroupDN&datastore=$DatastoreName&defer_create=1&enable_version=0&path=cloudvolumes/writable&template_name=template_UIA_only_80.vmdk&template_path=cloudvolumes/writable_templates&uniq_string=$Datastore_uniq_string
    Encoded:
    create%26bg%3D1%26block_login%3D1%26create_for%5B0%5D%5Bentity_type%5D%3DGroup%26create_for%5B0%5D%5Bname%5D%3D%24GroupName%26create_for%5B0%5D%5Bpath%5D%3D%24GroupDN%26datastore%3D%24DatastoreName%26defer_create%3D1%26enable_version%3D0%26path%3Dcloudvolumes%2Fwritable%26template_name%3Dtemplate_UIA_only_80.vmdk%26template_path%3Dcloudvolumes%2Fwritable_templates%26uniq_string%3D%24Datastore_uniq_string

    Like

    1. Made changes but still failing at Bad Request 400 😦

      $createWVcmd = “http://ENG-appvol-HACL-pod1.ldn.abcprod.local/cv_api/writables?action=create%26bg%3D1%26block_login%3D0%26create_for%5B0%5D%5Bentity_type%5D%3DGroup%26create_for%5B0%5D%5Bname%5D%3D%24App_Global_VM_A3_HACL_POD4_vNext_L_WRT_A%26create_for%5B0%5D%5Bpath%5D%3D%24CN=App_Global_VM_A3_HACL_POD4_vNext_L_WRT_A,OU=APPV Eng Test,OU=PraneshTest,OU=Testing,DC=abc,DC=wintel,DC=abcprod,DC=local%26datastore%3D%24LDNC008_AP_APV_19_N1D%26defer_create%3D1%26enable_version%3D0%26path%3Dcloudvolumes%2Fwritable%26template_name%3Dtemplate_UIA_only_80.vmdk%26template_path%3Dcloudvolumes%2Fwritable_templates%26uniq_string%3D%24datastore-107|HACL_POD1|1”

      Invoke-API -uri $createWVcmd -verb “POST” -params $null -sessionstore $sessStore

      I am using the following:

      entity_type = Group

      groupName = App_Global_VM_A3_HACL_POD4_vNext_L_WRT_A

      path = CN=App_Global_VM_A3_HACL_POD4_vNext_L_WRT_A,OU=APPV Eng Test,OU=CreateWVTest,OU=Testing,DC=abc,DC=wintel,DC=abcprod,DC=local

      DatastoreName = LDNC008_AP_APV_19_N1D

      DataStore Unique String = datastore-107|HACL_POD1|1

      Like

      1. hmm, not sure. Here is a string that works:

        action=create&datastore=S1-AppVolumes-1&uniq_string=datastore-47%7CS1-DC%7C1&path=cloudvolumes%2Fwritable&template_path=cloudvolumes%2Fwritable_templates&template_name=template_profile_only.vmdk&mount_prefix=&enable_version=0&defer_create=0&bg=1&create_for%5B0%5D%5Bentity_type%5D=User&create_for%5B0%5D%5Bname%5D=Chris+Halstead&create_for%5B0%5D%5Bpath%5D=CN%3DChris+Halstead%2CCN%3DUsers%2CDC%3Dtest%2CDC%3Dcom&block_login=0&error_action=disable_and_alert&id=&size_mb=

        try developer tools in the browser and postman to troubleshoot.

        Like

      2. Thanks Chris for all the help I could get. I really appreciate it.
        Finally Fixed it. All working fine. Post updated.

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: