Powershell script to install VPP application on devices

Powershell script to install VPP application on multiply devices.

To migrate Public apps to VPP apps when VPP apps management is not available in Airwatch console (known Airwatch v8 bug AAPP-1371) you can use following script.

Prerequsits:

  1. username and password for an account that has Airwatch role “Rest API Service”
  2. rest api token - get from Airwatch portal for target group: settings → system → Advanced → API → REST API
  3. input csv with target devices Airwatch Id (DeviceId)
  4. application name - should be converted to html format (replace " " with %20 etc) - (note: to be improved)
  5. local group name
  6. paths for log files

Assumptions:

  1. Counted cycle construction been used to push VPP app to device. Counter based on attribute that ANY device has assigned, Device Id or similar (UDID)
  2. Structure of Airwatch data output to Powershell PScutom object is the same as listed in parsing functions

Script:

$password = Read-Host -Prompt 'Enter your AW password' -AsSecureString
$userName = "XXXXXXXXXXXXXXXXXXXXXXXX"
$tenantAPIKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$airwatchServer = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$Platform = "Apple"
$PUBlocationGroupName = "XXXXXXXXXX"
$VPPlocationGroupName = "XXXXXXXXXXXXXXXXXXXXXXX"
$VPPapplicationname = XXXXXXXXXXXXXXXXXXXX"
$PUBapplicationname = "XXXXXXXXXXXXXXXXXXXXX"
$InputCSVwithDEviceIDs = "XXXXXXXXX_public_installed_repush.csv"
$OutFilesuccessLog = "XXXXXXXXXXX\vpp_app_push_log_success.log"
$OutFilefailureLog = "XXXXXXXXXXX\vpp_app_push_log_failure.log"
$OutFileErrorLog = "XXXXXXXXXXX\vpp_app_push_log_error.log"

Function Get-BasicUserForAuth `Preformatted text`{

	Param([string]$func_username)

	$userNameWithPassword = $func_username
	$encoding = [System.Text.Encoding]::ASCII.GetBytes($userNameWithPassword)
	$encodedString = [Convert]::ToBase64String($encoding)

	Return "Basic " + $encodedString
}

Function Build-Headers {

    Param([string]$authoriztionString, [string]$tenantCode, [string]$acceptType, [string]$contentType)

    $authString = $authoriztionString
    $tcode = $tenantCode
    $accept = $acceptType
    $content = $contentType
    $header = @{"Authorization" = $authString; "aw-tenant-code" = $tcode; "Accept" = $useJSON; "Content-Type" = $useJSON}
     
    Return $header
}

Function Parse-ApplicationObject {
		
	param([PSObject]$currentApplication, [string]$ObjectName)
	
	$Application = New-Object -TypeName PSObject
	$Application | Add-Member -MemberType NoteProperty -Name ApplicationName -value "" -Force
	$Application | Add-Member -type NoteProperty -Name Status -value "" -Force
	$Application | Add-Member -type NoteProperty -Name BundleId -value "" -Force
	$Application | Add-Member -type NoteProperty -Name LocationGroupId -value "" -Force
	$Application | Add-Member -type NoteProperty -Name IDValue -value "" -Force
	$Application.ApplicationName = $currentApplication.ApplicationName
	$Application.Status = $currentApplication.Assignments.Status
    $Application.BundleId = $currentApplication.BundleId
    $Application.LocationGroupId = $currentApplication.LocationGroupId
    $Application.IDValue = $currentApplication.Id.Value
	New-Variable -Name $ObjectName -Value $Application -Scope Script
}

Function Parse-LocationGroupObject {
		
	param([PSObject]$LocationGroupOutput, [string]$ObjectName)
	
	$LocationGroupObject = New-Object -TypeName PSObject
	$LocationGroupObject | Add-Member -type NoteProperty -Name Name -value "" -Force
	$LocationGroupObject | Add-Member -type NoteProperty -Name LocationGroupIdValue -value "" -Force
	$LocationGroupObject | Add-Member -type NoteProperty -Name Users -value "" -Force
	$LocationGroupObject | Add-Member -type NoteProperty -Name Devices -value "" -Force
	$LocationGroupObject.Name = $LocationGroupOutput.Name
    $LocationGroupObject.LocationGroupIdValue = $LocationGroupOutput.Id.Value
	$LocationGroupObject.Users = $LocationGroupOutput.Users
	$LocationGroupObject.Devices = $LocationGroupOutput.Devices
	New-Variable -Name $ObjectName -Value $LocationGroupObject -Scope Script
}


Function Parse-DeviceObject {
		
	param([PSObject]$DeviceOutput, [string]$ObjectName)
	
	$DeviceObject = New-Object -TypeName PSObject
	$DeviceObject | Add-Member -type NoteProperty -Name DeviceFriendlyName -value "" -Force
	$DeviceObject | Add-Member -type NoteProperty -Name SerialNumber -value "" -Force
	$DeviceObject | Add-Member -type NoteProperty -Name DeviceIDValue -value "" -Force
	$DeviceObject | Add-Member -type NoteProperty -Name LocationGroupIdValue -value "" -Force
	$DeviceObject | Add-Member -type NoteProperty -Name LocationGroupIdName -value "" -Force
	$DeviceObject.DeviceFriendlyName = $DeviceOutput.DeviceFriendlyName
	$DeviceObject.SerialNumber = $DeviceOutput.SerialNumber
	$DeviceObject.DeviceIDValue = $DeviceOutput.Id.Value
	$DeviceObject.LocationGroupIdValue = $DeviceOutput.LocationGroupId.Id.Value
	$DeviceObject.LocationGroupIdName = $DeviceOutput.LocationGroupId.Name
	New-Variable -Name $ObjectName -Value $DeviceObject -Scope Script
}

Function Parse-DeviceAppsObject {
		
	param([PSObject]$DeviceAppsOutput, [string]$ObjectName)
	
   	$DeviceAppsObject = New-Object -TypeName PSObject
	$DeviceAppsObject | Add-Member -type NoteProperty -Name ApplicationName -value "" -Force
	$DeviceAppsObject | Add-Member -type NoteProperty -Name ApplicationIdentifier -value "" -Force
	$DeviceAppsObject | Add-Member -type NoteProperty -Name Type -value "" -Force
	$DeviceAppsObject | Add-Member -type NoteProperty -Name IsManaged -value "" -Force
	$DeviceAppsObject | Add-Member -type NoteProperty -Name IdValue -value "" -Force
	$DeviceAppsObject.ApplicationName = $DeviceAppsOutput.DeviceApps.ApplicationName
	$DeviceAppsObject.ApplicationIdentifier = $DeviceAppsOutput.DeviceApps.ApplicationIdentifier
	$DeviceAppsObject.Type = $DeviceAppsOutput.DeviceApps.Type
	$DeviceAppsObject.IsManaged = $DeviceAppsOutput.DeviceApps.IsManaged
	$DeviceAppsObject.IdValue = $DeviceAppsOutput.DeviceApps.Id.Value
	New-Variable -Name $ObjectName -Value $DeviceAppsObject -Scope Script
}


$concateUserInfo = $userName + ":" + [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
$restUserName = Get-BasicUserForAuth ($concateUserInfo)
$useJSON = "application/json"
$headers = Build-Headers $restUserName $tenantAPIKey $useJSON $useJSON

# Getting VPP location group ID by the group name
Remove-variable -Name "VPPTargetLocationGroup" -Force -EA SilentlyContinue
Clear-variable -Name "URIstring"  -Force -EA SilentlyContinue
$URIstring = $airwatchServer + "/api/system/groups/search?name=" + $VPPlocationGroupName
$VPPLocationGroupOutput = Invoke-RestMethod -Method Get -Uri $URIstring -Headers $headers
Parse-LocationGroupObject $VPPLocationGroupOutput.Locationgroups "VPPTargetLocationGroup"


# Getting PUB application BundleID by the app name

$PUBApplicationBundleIDValues = @()
foreach ($LocationGroupIdValue in $VPPTargetLocationGroup.LocationGroupIdValue){
	Clear-variable -Name "URIstring"  -Force -EA SilentlyContinue
	Remove-variable -Name "PUBappArray"  -Force -EA SilentlyContinue
	$URIstring = $airwatchServer + "/api/mam/apps/search?type=App&applicationtype=Public&locationgroupid=" + $LocationGroupIdValue + "&applicationname=" + $PUBapplicationname + "&platform=" + $Platform
	$PUBapp = Invoke-RestMethod -Method Get -Uri $URIstring -Headers $headers
	Parse-ApplicationObject $PUBapp.Application "PUBappArray"
	$PUBApplicationBundleIDValue = $PUBappArray.BundleId
	$PUBApplicationBundleIDValues = $PUBApplicationBundleIDValues + $PUBApplicationBundleIDValue
}
If ( ($PUBApplicationBundleIDValues | Select -Unique).Count -eq 1 ) { $PUBApplicationBundleIDValue = $PUBApplicationBundleIDValues[0] } Else { Write-Host 'Verify public app groups belonging'}


# Getting VPP App ID and Status and checking consistency across all location groups
$VPPApplicationIDValues = @()
$VPPApplicationBundleIdValues = @()
$VPPApplicationStatuses = @()
$VPPApplicationLocationGroupIds = @()
foreach ($LocationGroupIdValue in $VPPTargetLocationGroup.LocationGroupIdValue){
	Clear-variable -Name "URIstring" -Force -EA SilentlyContinue
	Remove-variable -Name "VPPappArray" -Force -EA SilentlyContinue
	$URIstring = $airwatchServer + "/api/mam/apps/purchased/search?applicationname=" + $VPPapplicationname + "&locationgroupid=" + $LocationGroupIdValue
	$VPPapp = Invoke-RestMethod -Method Get -Uri $URIstring -Headers $headers
	Parse-ApplicationObject $VPPapp.Application "VPPappArray"
	$VPPApplicationIDValue = $VPPappArray.IDValue
	$VPPApplicationIDValues = $VPPApplicationIDValues + $VPPApplicationIDValue
	$VPPApplicationBundleIdValue = $VPPappArray.BundleId
	$VPPApplicationBundleIdValues = $VPPApplicationBundleIdValues + $VPPApplicationBundleIdValue
	$VPPApplicationStatus = $VPPappArray.Status
	$VPPApplicationStatuses = $VPPApplicationStatuses + $VPPApplicationStatus
	$VPPApplicationLocationGroupId = $VPPappArray.LocationGroupId
	$VPPApplicationLocationGroupIds = $VPPApplicationLocationGroupIds + $VPPappArray.LocationGroupId
}
If ((($VPPApplicationIDValues | Select -Unique).Count -eq 1) -and (($VPPApplicationLocationGroupIds | Select -Unique).Count -eq 1) -and (($VPPApplicationBundleIdValues | Select -Unique).Count -eq 1)) { 
	$VPPApplicationIDValue = $VPPApplicationIDValues[0] 
	$VPPApplicationLocationGroupId = $VPPApplicationLocationGroupIds[0]
	$VPPApplicationBundleIdValue = $VPPApplicationBundleIdValues[0] } else { Write-Host 'Verify assignment groups' }
If ( ($VPPApplicationStatuses | Select -Unique).Count -eq 1 ) { $VPPApplicationStatus = $VPPApplicationStatuses[0] } Else { Write-Host 'Verify if VPP App assigned' }

# Read Devices IDs from CSV, getting all apps installed on Device, checking by app id if public app is installed on device, and if it is then pushing to the device VPP app
Import-Csv -Delimiter ‘,’ $InputCSVwithDEviceIDs | ForEach-Object{

	$DeviceID = $_.DeviceID
	$DevceUser = $_.User

		Clear-variable -Name "URIstring" -Force -EA SilentlyContinue
		Remove-variable -Name "DeviceAppsArray" -Force -EA SilentlyContinue
		$URIstring = $airwatchServer + "/api/mdm/devices/" + $DeviceID + "/apps?"
		$DeviceAppsOutput = Invoke-RestMethod -Method Get -Uri $URIstring -Headers $headers
		Parse-DeviceAppsObject $DeviceAppsOutput "DeviceAppsArray"
		if (($DeviceAppsArray.ApplicationIdentifier -contains $PUBApplicationBundleIDValue) -and ($VPPApplicationStatus -eq "Active")) {
			Clear-variable -Name "URIstring"
			$DevicesBody = @{"DeviceId" = "$DeviceID"}
			$Error.Clear()
			$URIstring = $airwatchServer + "/api/mam/apps/purchased/" + $VPPApplicationIDValue + "/install"
			Invoke-RestMethod -Method Post -Uri $URIstring -Headers $headers -body (ConvertTo-Json $DevicesBody)
				if ([string]::IsNullOrWhiteSpace($Error[0])) {
						[string]$out = (get-date -Format 'dd/MM/yyyy HH:MM') + " VPP Application " + $PUBApplicationBundleIDValue + " been pushed to Device with DeviceID " + $DeviceID + " user " + $DevceUser		
						$out | Out-File $OutFilesuccessLog -Append -Force	
				}
				else {
						[string]$out = (get-date -Format 'dd/MM/yyyy HH:MM') + " DeviceID " + $DeviceID + " user " + $DevceUser + "  " + ($Error[0])
						$out | Out-File $OutFileErrorLog -Append -Force
				}
		}
		else {
		[string]$out = (get-date -Format 'dd/MM/yyyy HH:MM') + " Device with DeviceID " + $DeviceID + " user " + $DevceUser + " doesn't have Public app with BundleId " + $PUBApplicationBundleIDValue + " or VPP Application " + $VPPapplicationname + " is not deployed to any group"
		$out | Out-File $OutFilefailureLog -Append -Force
		}
}

# Getting device IDs with instance of VPP app - currently broken in Airwatch v8.4.8 and resulting in 1 random DeviceId
Clear-variable -Name "URIstring" -Force -EA SilentlyContinue
Remove-variable -Name "DevwithVPPapp" -Force -EA SilentlyContinue
$URIstring = $airwatchServer + "/api/mam/apps/purchased/" + $VPPApplicationIDValue + "/devices?status=Installed&locationgroupid=" + $VPPApplicationLocationGroupId
$DevwithVPPapp = Invoke-RestMethod -Method Get -Uri $URIstring -Headers $headers
$DevwithVPPapp