|
| 1 | +function Add-CIPPW32ScriptApplication { |
| 2 | + <# |
| 3 | + .SYNOPSIS |
| 4 | + Adds a Win32 app with PowerShell script installer to Intune. |
| 5 | +
|
| 6 | + .DESCRIPTION |
| 7 | + Creates a Win32 app using the PowerShell script installer feature. |
| 8 | + Uploads an intunewin file and PowerShell scripts via the scripts endpoint. |
| 9 | +
|
| 10 | + .PARAMETER TenantFilter |
| 11 | + Tenant ID or domain name for the Graph API call. |
| 12 | +
|
| 13 | + .PARAMETER Properties |
| 14 | + PSCustomObject containing all Win32 app properties: |
| 15 | + - displayName (required): Display name of the app |
| 16 | + - description: Description of the app |
| 17 | + - publisher: Publisher name |
| 18 | + - installScript (required): PowerShell install script content (plaintext) |
| 19 | + - uninstallScript: PowerShell uninstall script content (plaintext) |
| 20 | + - detectionScript: PowerShell detection script content (plaintext) |
| 21 | + - runAsAccount: 'system' or 'user' (default: 'system') |
| 22 | + - deviceRestartBehavior: 'allow', 'suppress', or 'force' (default: 'suppress') |
| 23 | + - runAs32Bit: Boolean, run scripts as 32-bit on 64-bit clients (default: false) |
| 24 | + - enforceSignatureCheck: Boolean, enforce script signature validation (default: false) |
| 25 | +
|
| 26 | + .PARAMETER FilePath |
| 27 | + Path to the intunewin file. |
| 28 | +
|
| 29 | + .PARAMETER FileName |
| 30 | + Name of the file from XML metadata. |
| 31 | +
|
| 32 | + .PARAMETER UnencryptedSize |
| 33 | + Unencrypted size of the file from XML metadata. |
| 34 | +
|
| 35 | + .PARAMETER EncryptionInfo |
| 36 | + Hashtable containing encryption information from XML. |
| 37 | +
|
| 38 | + .EXAMPLE |
| 39 | + $Properties = @{ |
| 40 | + displayName = 'My Script App' |
| 41 | + installScript = 'Write-Host "Installing..."' |
| 42 | + } |
| 43 | + $EncryptionInfo = @{ EncryptionKey = '...'; MacKey = '...'; ... } |
| 44 | + Add-CIPPW32ScriptApplication -TenantFilter 'contoso.com' -Properties $Properties -FilePath 'app.intunewin' -FileName 'app.intunewin' -UnencryptedSize 1024000 -EncryptionInfo $EncryptionInfo |
| 45 | + #> |
| 46 | + [CmdletBinding()] |
| 47 | + param( |
| 48 | + [Parameter(Mandatory = $true)] |
| 49 | + [string]$TenantFilter, |
| 50 | + |
| 51 | + [Parameter(Mandatory = $true)] |
| 52 | + [PSCustomObject]$Properties, |
| 53 | + |
| 54 | + [Parameter(Mandatory = $true)] |
| 55 | + [string]$FilePath, |
| 56 | + |
| 57 | + [Parameter(Mandatory = $true)] |
| 58 | + [string]$FileName, |
| 59 | + |
| 60 | + [Parameter(Mandatory = $true)] |
| 61 | + [int64]$UnencryptedSize, |
| 62 | + |
| 63 | + [Parameter(Mandatory = $true)] |
| 64 | + [hashtable]$EncryptionInfo |
| 65 | + ) |
| 66 | + |
| 67 | + # Build Win32 app body |
| 68 | + $intuneBody = @{ |
| 69 | + '@odata.type' = '#microsoft.graph.win32LobApp' |
| 70 | + displayName = $Properties.displayName |
| 71 | + description = $Properties.description |
| 72 | + publisher = $Properties.publisher |
| 73 | + fileName = $FileName |
| 74 | + setupFilePath = 'N/A' |
| 75 | + minimumSupportedWindowsRelease = '1607' |
| 76 | + returnCodes = @( |
| 77 | + @{ returnCode = 0; type = 'success' } |
| 78 | + @{ returnCode = 1707; type = 'success' } |
| 79 | + @{ returnCode = 3010; type = 'softReboot' } |
| 80 | + @{ returnCode = 1641; type = 'hardReboot' } |
| 81 | + @{ returnCode = 1618; type = 'retry' } |
| 82 | + ) |
| 83 | + } |
| 84 | + |
| 85 | + # Add install experience |
| 86 | + $intuneBody.installExperience = @{ |
| 87 | + '@odata.type' = 'microsoft.graph.win32LobAppInstallExperience' |
| 88 | + runAsAccount = if ($Properties.runAsAccount) { $Properties.runAsAccount } else { 'system' } |
| 89 | + deviceRestartBehavior = if ($Properties.deviceRestartBehavior) { $Properties.deviceRestartBehavior } else { 'suppress' } |
| 90 | + maxRunTimeInMinutes = 60 |
| 91 | + } |
| 92 | + |
| 93 | + # Create the app |
| 94 | + $Baseuri = 'https://graph.microsoft.com/beta/deviceAppManagement/mobileApps' |
| 95 | + $NewApp = New-GraphPostRequest -Uri $Baseuri -Body ($intuneBody | ConvertTo-Json -Depth 10) -Type POST -tenantid $TenantFilter |
| 96 | + Start-Sleep -Milliseconds 200 |
| 97 | + |
| 98 | + # Upload intunewin file using shared helper |
| 99 | + Add-CIPPWin32LobAppContent -AppId $NewApp.id -FilePath $FilePath -FileName $FileName -UnencryptedSize $UnencryptedSize -EncryptionInfo $EncryptionInfo -TenantFilter $TenantFilter |
| 100 | + |
| 101 | + # Upload PowerShell scripts via the scripts endpoint |
| 102 | + $RunAs32Bit = if ($null -ne $Properties.runAs32Bit) { [bool]$Properties.runAs32Bit } else { $false } |
| 103 | + $EnforceSignatureCheck = if ($null -ne $Properties.enforceSignatureCheck) { [bool]$Properties.enforceSignatureCheck } else { $false } |
| 104 | + |
| 105 | + $InstallScriptId = $null |
| 106 | + $UninstallScriptId = $null |
| 107 | + |
| 108 | + if ($Properties.installScript) { |
| 109 | + $InstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Properties.installScript)) |
| 110 | + $InstallScriptBody = @{ |
| 111 | + '@odata.type' = '#microsoft.graph.win32LobAppInstallPowerShellScript' |
| 112 | + displayName = 'install.ps1' |
| 113 | + enforceSignatureCheck = $EnforceSignatureCheck |
| 114 | + runAs32Bit = $RunAs32Bit |
| 115 | + content = $InstallScriptContent |
| 116 | + } | ConvertTo-Json |
| 117 | + |
| 118 | + $InstallScriptResponse = New-GraphPostRequest -Uri "$Baseuri/$($NewApp.id)/microsoft.graph.win32LobApp/contentVersions/1/scripts" -Body $InstallScriptBody -Type POST -tenantid $TenantFilter |
| 119 | + $InstallScriptId = $InstallScriptResponse.id |
| 120 | + |
| 121 | + # Wait for script to be committed |
| 122 | + do { |
| 123 | + $ScriptState = New-GraphGetRequest -Uri "$Baseuri/$($NewApp.id)/microsoft.graph.win32LobApp/contentVersions/1/scripts/$InstallScriptId" -tenantid $TenantFilter |
| 124 | + if ($ScriptState.state -like '*fail*') { |
| 125 | + throw "Failed to commit install script: $($ScriptState.state)" |
| 126 | + } |
| 127 | + Start-Sleep -Milliseconds 300 |
| 128 | + } while ($ScriptState.state -eq 'commitPending') |
| 129 | + } |
| 130 | + |
| 131 | + if ($Properties.uninstallScript) { |
| 132 | + $UninstallScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Properties.uninstallScript)) |
| 133 | + $UninstallScriptBody = @{ |
| 134 | + '@odata.type' = '#microsoft.graph.win32LobAppUninstallPowerShellScript' |
| 135 | + displayName = 'uninstall.ps1' |
| 136 | + enforceSignatureCheck = $EnforceSignatureCheck |
| 137 | + runAs32Bit = $RunAs32Bit |
| 138 | + content = $UninstallScriptContent |
| 139 | + } | ConvertTo-Json |
| 140 | + |
| 141 | + $UninstallScriptResponse = New-GraphPostRequest -Uri "$Baseuri/$($NewApp.id)/microsoft.graph.win32LobApp/contentVersions/1/scripts" -Body $UninstallScriptBody -Type POST -tenantid $TenantFilter |
| 142 | + $UninstallScriptId = $UninstallScriptResponse.id |
| 143 | + |
| 144 | + # Wait for script to be committed |
| 145 | + do { |
| 146 | + $ScriptState = New-GraphGetRequest -Uri "$Baseuri/$($NewApp.id)/microsoft.graph.win32LobApp/contentVersions/1/scripts/$UninstallScriptId" -tenantid $TenantFilter |
| 147 | + if ($ScriptState.state -like '*fail*') { |
| 148 | + throw "Failed to commit uninstall script: $($ScriptState.state)" |
| 149 | + } |
| 150 | + Start-Sleep -Milliseconds 300 |
| 151 | + } while ($ScriptState.state -eq 'commitPending') |
| 152 | + } |
| 153 | + |
| 154 | + # Build final commit body with active script references |
| 155 | + $CommitBody = @{ |
| 156 | + '@odata.type' = '#microsoft.graph.win32LobApp' |
| 157 | + committedContentVersion = '1' |
| 158 | + } |
| 159 | + |
| 160 | + if ($InstallScriptId) { |
| 161 | + $CommitBody['activeInstallScript'] = @{ targetId = $InstallScriptId } |
| 162 | + } |
| 163 | + |
| 164 | + if ($UninstallScriptId) { |
| 165 | + $CommitBody['activeUninstallScript'] = @{ targetId = $UninstallScriptId } |
| 166 | + } |
| 167 | + |
| 168 | + # Add detection rules if provided |
| 169 | + if ($Properties.detectionScript) { |
| 170 | + $DetectionScriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Properties.detectionScript)) |
| 171 | + $CommitBody['detectionRules'] = @( |
| 172 | + @{ |
| 173 | + '@odata.type' = '#microsoft.graph.win32LobAppPowerShellScriptDetection' |
| 174 | + scriptContent = $DetectionScriptContent |
| 175 | + enforceSignatureCheck = $EnforceSignatureCheck |
| 176 | + runAs32Bit = $RunAs32Bit |
| 177 | + } |
| 178 | + ) |
| 179 | + } |
| 180 | + |
| 181 | + # Commit the app with script references |
| 182 | + $null = New-GraphPostRequest -Uri "$Baseuri/$($NewApp.id)" -tenantid $TenantFilter -Body ($CommitBody | ConvertTo-Json -Depth 10) -Type PATCH |
| 183 | + |
| 184 | + return $NewApp |
| 185 | + |
| 186 | +} |
0 commit comments