Skip to content

Commit fb40dd5

Browse files
committed
.
1 parent 4bccd8c commit fb40dd5

File tree

5 files changed

+188
-0
lines changed

5 files changed

+188
-0
lines changed

Scripts/avppLicenseReleaser/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# avppLicenseReleaser
2+
I needed to have all VPP app licenses released in order to remove the associated location in Apple Business Manager. GGing through hundreds of apps with thousands of associated licenses was not a task I wanted to do manually, and I was unable to find any documented API way to do it, so I made this.
3+
4+
Most actions done through the Admin Web Console is actually just doing API calls, so looking at the calls that were being sent while doing the steps manually, let me create these functions. As for the authentication I just took the lazy way and logs in through the browser then simply copies the session ID from the browser session to my script.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function Get-VppApp {
2+
[CmdletBinding()]
3+
param(
4+
[Parameter()]
5+
[string]
6+
$Query
7+
,
8+
[Parameter()]
9+
[int]
10+
$Limit = 10
11+
)
12+
Write-Verbose -Message "$($MyInvocation.MyCommand.Name)"
13+
14+
$Index = 0
15+
do {
16+
$Attributes = @{
17+
Method = 'POST'
18+
Uri = "$($Script:Config.Uri.AbsoluteUri)controlpoint/rest/application/filter/appandfilter"
19+
Headers = $Script:Config.Headers
20+
WebSession = $Script:Config.WebSession
21+
Body = "start=$($Index)&limit=$($Limit)&sortOrder=ASC&sortColumn=name&enableCount=true&filterIds=%5B%22application.type.store%22%5D&search=$([uri]::EscapeDataString($Query))"
22+
}
23+
try {
24+
$Response = Invoke-WebRequest @Attributes
25+
}
26+
catch {
27+
throw $_
28+
}
29+
$Data = $Response.Content | ConvertFrom-Json
30+
$Data.result.listData.list
31+
$Index += $Limit
32+
} while ($Data.result.listData.list.Count -gt 0)
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function Get-VppAppLicense {
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Mandatory = $true
5+
, Position = 0
6+
, ValueFromPipeline = $true
7+
, ValueFromPipelineByPropertyName = $true
8+
, ValueFromRemainingArguments = $false)]
9+
[int]
10+
$Id
11+
)
12+
begin {
13+
Write-Verbose -Message "$($MyInvocation.MyCommand.Name)"
14+
$Attributes = @{
15+
Method = 'GET'
16+
Headers = $Script:Config.Headers
17+
WebSession = $Script:Config.WebSession
18+
}
19+
}
20+
process {
21+
$Attributes.Uri = "$($Script:Config.Uri.AbsoluteUri)controlpoint/rest/application/appstore/$($Id)"
22+
try {
23+
$Response = Invoke-WebRequest @Attributes
24+
}
25+
catch {
26+
throw $_
27+
}
28+
$Data = $Response.Content | ConvertFrom-Json
29+
$PlatformData = $Data.result.platformData
30+
$Platforms = ($PlatformData.psobject.Members | Where-Object { $_.MemberType -eq 'NoteProperty' }).Name
31+
$Associations = @()
32+
foreach ($Platform in $Platforms) {
33+
$Associations += $PlatformData.${Platform}.avppTokenParams.avppTokenRecordParams
34+
}
35+
$Associations | Sort-Object -Unique -Property licenseId
36+
}
37+
end {}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
function Revoke-VppAppLicense {
2+
[CmdletBinding(SupportsShouldProcess = $true
3+
, ConfirmImpact = 'High')]
4+
param(
5+
[Parameter(Mandatory = $true
6+
, Position = 0
7+
, ValueFromPipeline = $true
8+
, ValueFromPipelineByPropertyName = $true
9+
, ValueFromRemainingArguments = $false)]
10+
[long[]]
11+
$LicenseId
12+
,
13+
[Parameter()]
14+
[int]
15+
$XdmID = 13
16+
,
17+
[Parameter()]
18+
[int]
19+
$StoreId = 1474254492
20+
,
21+
[Parameter()]
22+
[int]
23+
$Start = 0
24+
,
25+
[Parameter()]
26+
[int]
27+
$Limit = 10
28+
,
29+
[Parameter()]
30+
[switch]
31+
$Force
32+
)
33+
begin {
34+
if ($Force -and !$PSBoundParameters.ContainsKey('Confirm')) {
35+
$ConfirmPreference = 'None'
36+
}
37+
Write-Verbose -Message "$($MyInvocation.MyCommand.Name)"
38+
$Attributes = @{
39+
Method = 'POST'
40+
Uri = "$($Script:Config.Uri.AbsoluteUri)controlpoint/rest/application/appstore/avpp/disassociate"
41+
Headers = $Script:Config.Headers
42+
WebSession = $Script:Config.WebSession
43+
}
44+
}
45+
process {
46+
for ($i = 0; $i -lt $LicenseId.Count; $i += $Limit) {
47+
$Data = @()
48+
$Data += "xdmId=$($XdmId)"
49+
$Data += "storeId=$($StoreId)"
50+
# Only revoke the $Limit number of licenses at a time.
51+
$IdSet = $LicenseId[$i..([math]::Min($i + $Limit - 1, $LicenseId.Count - 1))]
52+
if ($PSCmdlet.ShouldProcess($IdSet -join ',')) {
53+
$IdSet | ForEach-Object { $Data += "licenseIds%5B%5D=$($_)" }
54+
$Attributes.Body = ($Data -join '&')
55+
try {
56+
$Response = Invoke-WebRequest @Attributes
57+
}
58+
catch {
59+
throw $_
60+
}
61+
}
62+
}
63+
}
64+
end {}
65+
}
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# dot-source functions
2+
$FunctionsPath = Join-Path -Path $PSScriptRoot -ChildPath 'functions'
3+
Get-ChildItem -Recurse -File -Path $FunctionsPath -Filter '*.ps1' |
4+
Where-Object { $_.BaseName -notmatch '(^\.|\.dev$|\.test$)' } |
5+
ForEach-Object {
6+
. $_.FullName
7+
}
8+
9+
# Update with the base URI for the XenMobile server, and the cookie value of JSESSIONID after logging into XenMobile in the browser.
10+
$Script:Config = @{
11+
Uri = [uri]'https://XENMOBILEHOST:4443'
12+
Session = @{
13+
Name = 'JSESSIONID'
14+
Value = 'THESESSIONIDGOESHERE'
15+
}
16+
}
17+
# Create web session object.
18+
$Session = New-Object -TypeName Microsoft.PowerShell.Commands.WebRequestSession
19+
$Cookie = New-Object -TypeName System.Net.Cookie($Script:Config.Session.Name, $Script:Config.Session.Value, '/', $Script:Config.Uri.Host)
20+
$Session.Cookies.Add($Cookie)
21+
$Script:Config.WebSession = $Session
22+
# Headers copied from a browsersession. Session specific values are retrived from above table.
23+
$Headers = @{
24+
'Accept' = 'application/json, text/javascript, */*; q=0.01'
25+
'Cache-Control' = 'no-cache'
26+
'Content-Type' = 'application/x-www-form-urlencoded'
27+
'Cookie' = "$($Script:Config.Session.Name)=$($Script:Config.Session.Value)"
28+
'Host' = "$($Script:Config.Uri.Host):$($Script:Config.Uri.Port)"
29+
'Origin' = "$($Script:Config.Uri.Scheme)://$($Script:Config.Uri.Host):$($Script:Config.Uri.Port)"
30+
'Pragma' = 'no-cache'
31+
'Referer' = $Script:Config.Uri.AbsoluteUri
32+
'Sec-Fetch-Dest' = 'empty'
33+
'Sec-Fetch-Mode' = 'cors'
34+
'Sec-Fetch-Site' = 'same-origin'
35+
'X-Requested-With' = 'XMLHttpRequest'
36+
}
37+
$Script:Config.Headers = $Headers
38+
$Exclude = @(
39+
3 # Citrix Secure Hub
40+
)
41+
# Get all VPP Apps from XenMobile |
42+
# exclude the app IDs in the $Exclude array |
43+
# Get the VPP licenses fro the app |
44+
# Revoke/release the VPP app license.
45+
Get-VppApp |
46+
Where-Object { $_.id -notin $Exclude } |
47+
Get-VppAppLicense |
48+
Revoke-VppAppLicense -Force

0 commit comments

Comments
 (0)