PowerShell Example: Create and Install an Agent

Goal

Provide an example PowerShell script that invokes the REST API and that performs a number of tasks, including:

  • Creates credentials
  • Creates Windows and Linux patch groups
  • Creates an agent policy with Windows and Linux tasks
  • Pushes an agent to all machines in a machine group
  • Invokes a task on an agent machine

Example Script

cls
#################################################################################
#
#                               DISCLAIMER: EXAMPLE ONLY
#
# Execute this example at your own risk. The console, target machines and  
# databases should be backed up prior to executing this example. Ivanti does 
# not warrant that the functions contained in this example will be
# uninterrupted or free of error. The entire risk as to the results and
# performance of this example is assumed by the person executing the example.
# Ivanti is not responsible for any damage caused by this example.
#
#################################################################################
# 
$isPS2 = $PSVersionTable.PSVersion.Major -eq 2
if($isPS2)
{
	[void][Reflection.Assembly]::LoadWithPartialName("System.Security") 
}
else
{
	Add-Type -AssemblyName "System.Security" > $null
}
# credentials for remote invokation of REST APIs
$remoteConsoleUsername = "remoteConsoleUsername"
$remoteConsolePassword = ConvertTo-SecureString "remoteConsolePassword" -AsPlainText -Force
$script:remoteConsoleCredential = New-Object -TypeName "PSCredential" -ArgumentList @($remoteConsoleUsername, $remoteConsolePassword)

# helper URIs for ISeC REST API
$consoleName = "consoleName"
$Uris =
@{
	Agents = "https://$($consoleName):3121/st/console/api/v1.0/agents"
	AgentDeployment = "https://$($consoleName):3121/st/console/api/v1.0/agents/deployment"
	CertificateConsole = "https://$($consoleName):3121/st/console/api/v1.0/configuration/certificate"
	Credentials = "https://$($consoleName):3121/st/console/api/v1.0/credentials"
	LinuxPatchDeploymentConfigurations = "https://$($consoleName):3121/st/console/api/v1.0/linux/patch/deploymentconfigurations"
	LinuxPatchGroups = "https://$($consoleName):3121/st/console/api/v1.0/linux/patch/groups"
	LinuxPatchScanConfigurations = "https://$($consoleName):3121/st/console/api/v1.0/linux/patch/scanconfigurations"
	MachineGroups = "https://$($consoleName):3121/st/console/api/v1.0/machinegroups"
	PatchGroups = "https://$($consoleName):3121/st/console/api/v1.0/patch/groups"
	Policies = "https://$($consoleName):3121/st/console/api/v1.0/policies"
	SessionCredentials = "https://$($consoleName):3121/st/console/api/v1.0/sessioncredentials"
}

#encrypts an array of bytes using RSA and the console certificate
function Encrypt-RSAConsoleCert
{
	param
	(
		[Parameter(Mandatory=$True, Position = 0)]
		[Byte[]]$ToEncrypt
	)
	try
	{
		$certResponse = Invoke-RestMethod $Uris.CertificateConsole -Method Get -Verbose -Credential $script:remoteConsoleCredential
		[Byte[]] $rawBytes = ([Convert]::FromBase64String($certResponse.derEncoded))
		$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(,$rawBytes)
		$rsaPublicKey = $cert.PublicKey.Key;
 
		$encryptedKey = $rsaPublicKey.Encrypt($ToEncrypt, $True);
		return $encryptedKey
	}
	finally
	{
		$cert.Dispose();
	}
}

# creates the body for creating credentials 
function Create-CredentialRequest
{
	param
	(
		[Parameter(Mandatory=$True, Position=0)]
		[String]$FriendlyName,
 
		[Parameter(Mandatory=$True, Position=1)]
		[String]$UserName,
 
		[Parameter(Mandatory=$True, Position=2)]
		[ValidateNotNull()]
		[SecureString]$Password
	)
 
	$body = @{ "userName" = $UserName; "name" = $FriendlyName; }
	$bstr = [IntPtr]::Zero;
	try
	{
		# Create an AES 128 Session key.
		$algorithm = [System.Security.Cryptography.Xml.EncryptedXml]::XmlEncAES128Url
		$aes = [System.Security.Cryptography.SymmetricAlgorithm]::Create($algorithm);
		$keyBytes = $aes.Key;
 
		# Encrypt the session key with the console cert
		$encryptedKey = Encrypt-RSAConsoleCert -ToEncrypt $keyBytes
		$session = @{ "algorithmIdentifier" = $algorithm; "encryptedKey" = [Convert]::ToBase64String($encryptedKey); "iv" = [Convert]::ToBase64String($aes.IV); }
 
		# Encrypt the password with the Session key.
		$cryptoTransform = $aes.CreateEncryptor();
 
		# Copy the BSTR contents to a byte array, excluding the trailing string terminator.
		$size = [System.Text.Encoding]::Unicode.GetMaxByteCount($Password.Length - 1);
 
		$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
		$clearTextPasswordArray = New-Object Byte[] $size
		[System.Runtime.InteropServices.Marshal]::Copy($bstr, $clearTextPasswordArray, 0, $size)
		$cipherText = $cryptoTransform.TransformFinalBlock($clearTextPasswordArray, 0 , $size)
 
		$passwordJson = @{ "cipherText" = $cipherText; "protectionMode" = "SessionKey"; "sessionKey" = $session }
	}
	finally
	{
		# Ensure All sensitive byte arrays are cleared and all crypto keys/handles are disposed.
		if ($clearTextPasswordArray -ne $null)
		{
			[Array]::Clear($clearTextPasswordArray, 0, $size)
		}
		if ($keyBytes -ne $null)
		{
			[Array]::Clear($keyBytes, 0, $keyBytes.Length);
		}
		if ($bstr -ne [IntPtr]::Zero)
		{
			[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
		}
		if ($cryptoTransform -ne $null)
		{
			$cryptoTransform.Dispose();
		}
		if ($aes -ne $null)
		{
			$aes.Dispose();
		}
	}
	$body.Add("password", $passwordJson)
	return $Body
}

# create body for creating session credential
$encryptedRemoteConsoleCredential = Create-CredentialRequest -FriendlyName "Encryped" -UserName $remoteConsoleUsername -Password $remoteConsolePassword
try
{
	# create session credential
	$sessionCredential = Invoke-RestMethod $Uris.SessionCredentials -body ($encryptedRemoteConsoleCredential.password | ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create windows patch group and add by multiple CVEs
	$windowsPatchGroup = Invoke-RestMethod -Uri $Uris.PatchGroups -Body( @{ Name = "Windows Patch Group" } | ConvertTo-Json ) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST
	$windowsPatchesResponse = Invoke-RestMethod ($windowsPatchGroup.links.self.href + "/patches/Cves") -body ( @{ Cves = "CVE-2019-0536","CVE-2017-11305"} |ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create linux patch group and add by multiple CVes
	$linuxPatchGroup = Invoke-RestMethod -uri $Uris.LinuxPatchGroups -Body ( @{ Name = "Linux Patch Group" } | ConvertTo-Json ) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST
	$linuxPatchesResponse = Invoke-RestMethod ($linuxPatchGroup.links.self.href + "/patches/Cves") -body ( @{ Cves ="CVE-2017-18267","CVE-2017-5461" } | ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create linux scan and deploy configurations
	$linuxScanConfiguration = Invoke-RestMethod -Uri $Uris.LinuxPatchScanConfigurations -Body ( @{ Name = "Linux Scan Configuration"; Filter = @{ PatchGroupIds = @( $($linuxPatchGroup.id) ) } } | ConvertTo-Json -Depth 20 )  -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST
	$linuxDeployConfiguration = Invoke-RestMethod -Uri $Uris.LinuxPatchDeploymentConfigurations -Body ( @{ Name = "Linux Deployment Configuration";  }| ConvertTo-Json -Depth 20 )  -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create policy
	$policy = Invoke-RestMethod -Uri $Uris.Policies -Body ( @{ Name = "Policy";  }| ConvertTo-Json -Depth 20 )  -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create linux patch task
	$linuxPatchTaskName = "Demo Linux Patch Task";
	$linuxPatchTask = Invoke-RestMethod -Uri ($policy.links.tasks.href + "/linuxpatch") -Body ( @{ Name = $linuxPatchTaskName; DeploymentEnabled = $false; ScanConfigurationId = $linuxScanConfiguration.Id  }| ConvertTo-Json -Depth 20 )  -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST
    
	# create windows patch task
	$windowsPatchTaskName = "Demo Windows Patch Task";
	$windowPatchTask = Invoke-RestMethod -Uri ($policy.links.tasks.href + "/windowspatch") -Body ( @{ Name = $windowsPatchTaskName ; DeploymentEnabled = $false; }| ConvertTo-Json -Depth 20 )  -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# create credentials for accessing both linux and windows machines
	$rootBody = Create-CredentialRequest -FriendlyName "root" -UserName "root" -Password $remoteConsolePassword
	$rootCred = Invoke-RestMethod $Uris.Credentials -Method Post -body ( $rootBody | ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json'
	$protectRemoteConsoleCredential = Invoke-RestMethod $Uris.Credentials -Method Post -body ( $encryptedRemoteConsoleCredential | ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json'
    
	# share and unshare credential with service example
	Invoke-RestMethod $rootCred.links.sharewithservice.href -Credential $script:remoteConsoleCredential -Method POST
	Invoke-RestMethod $rootCred.links.sharewithservice.href -Credential $script:remoteConsoleCredential -Method DELETE

	# create machine group with both linux and windows machines
	$linuxIPAddress = "linuxIPAddress";
	$windowsMachineName = "WindowsMachineName"
	$machineGroupBody =
	@{
		name = "Machine group"
		CredentialId = $rootCred.id
		discoveryFilters =  @(
		@{
			category = "IPAddress";
			name = $linuxIPAddress;
		},
		@{
			category = "MachineName";
			name = $windowsMachineName;
			AdminCredentialId = $protectRemoteConsoleCredential.id;
		})
	}
	$machineGroup = Invoke-RestMethod $Uris.MachineGroups -Body ( $machineGroupBody | ConvertTo-Json -Depth 20) -Credential $script:remoteConsoleCredential -ContentType 'application/json' -Method POST

	# push an agent to all systems in created machine group with the created policy
	$agentInput =
	@{
		PolicyId = $policy.Id;
		MachineGroupIds = @($machineGroup.Id);
	}

	$startTime = [DateTime]::Now
	$TimeoutMinutes = 5

	$agentDeployment = Invoke-WebRequest $Uris.AgentDeployment -Method Post -Body ( $agentInput | ConvertTo-Json -Depth 20 ) -UseBasicParsing -Credential $script:remoteConsoleCredential -ContentType 'application/json'

	Write-Verbose -Verbose ("Installing agent to machine group " + $machineGroup.name)
	#wait for agent deployment to complete
	$agentOperationLocation = $agentDeployment.Headers["Operation-Location"]
	$agentOperationResult = Invoke-RestMethod $agentOperationLocation -Method Get -Credential $script:remoteConsoleCredential
    
	while ($agentOperationResult.Status -eq 'Running' -or $agentOperationResult.Status -eq 'NotStarted')
	{
		if ([DateTime]::Now -gt $startTime.AddMinutes($TimeoutMinutes))
		{
			throw "Timed out waiting for operation to complete"
		}
		Start-Sleep 5
		$agentOperationResult = Invoke-RestMethod $agentOperationLocation -Method Get -Credential $script:remoteConsoleCredential
	}

	if( $agentOperationResult.Status -ne "Succeeded")
	{
		throw "Installing agent to machines in " + $machineGroup.name + " " + $agentOperationResult.status;
	}
	Write-Verbose -Verbose ("Finished installing to machines in " + $machineGroup.name)
    
	# get all agents
	$allAgents = Invoke-RestMethod $Uris.Agents -Credential $script:remoteConsoleCredential
    
	# filter to just windows agent
	$agent = $allAgents.value | Where-Object { $_.MachineName -eq $windowsMachineName }

	# make windows agent check in
	$result = Invoke-RestMethod $agent.links.checkin.href -Credential $script:remoteConsoleCredential -Method POST
    
	# get all tasks
	$allTasks = Invoke-RestMethod $agent.links.tasks.href -Credential $script:remoteConsoleCredential
    
	# fitler to windows patch task
	$invokablePatchTask = $allTasks.value | Where-Object { $_.taskName -eq $windowsPatchTaskName }
    
	# invoke patch task
	$result = Invoke-RestMethod $invokablePatchTask.links.self.href -Credential $script:remoteConsoleCredential -Method POST
}
finally
{
	# remote session credential
	Invoke-RestMethod $Uris.SessionCredentials -Credential $script:remoteConsoleCredential -Method DELETE
}