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:
- username and password for an account that has Airwatch role “Rest API Service”
- rest api token - get from Airwatch portal for target group: settings → system → Advanced → API → REST API
- input csv with target devices Airwatch Id (DeviceId)
- application name - should be converted to html format (replace " " with %20 etc) - (note: to be improved)
- local group name
- paths for log files
Assumptions:
- 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)
- 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