11function Add-CIPPW32ScriptApplication {
22 <#
33 . SYNOPSIS
4- Adds a Win32 app with PowerShell script installer to Intune.
4+ Adds a Win32 app with PowerShell script installer to Intune using the standard Chocolatey package .
55
66 . DESCRIPTION
7- Creates a Win32 app using the PowerShell script installer feature .
8- Uploads an intunewin file and PowerShell scripts via the scripts endpoint .
7+ Creates a Win32 app that uses the standard Chocolatey intunewin package but with custom PowerShell scripts .
8+ Always uploads the same Choco package, but uses user-provided scripts for install/uninstall commands .
99
1010 . PARAMETER TenantFilter
1111 Tenant ID or domain name for the Graph API call.
@@ -17,91 +17,134 @@ function Add-CIPPW32ScriptApplication {
1717 - publisher: Publisher name
1818 - installScript (required): PowerShell install script content (plaintext)
1919 - uninstallScript: PowerShell uninstall script content (plaintext)
20- - detectionScript: PowerShell detection script content (plaintext)
20+ - detectionPath (required): Full path to the file or folder to detect (e.g., 'C:\\Program Files\\MyApp')
21+ - detectionFile: File name to detect (optional, for folder path detection)
22+ - detectionType: 'exists', 'modifiedDate', 'createdDate', 'version', 'sizeInMB' (default: 'exists')
23+ - check32BitOn64System: Boolean, check 32-bit registry/paths on 64-bit systems (default: false)
2124 - runAsAccount: 'system' or 'user' (default: 'system')
2225 - 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.
3726
3827 . EXAMPLE
3928 $Properties = @{
4029 displayName = 'My Script App'
4130 installScript = 'Write-Host "Installing..."'
31+ detectionPath = 'C:\\Program Files\\MyApp'
32+ detectionFile = 'app.exe'
4233 }
43- $EncryptionInfo = @{ EncryptionKey = '...'; MacKey = '...'; ... }
44- Add-CIPPW32ScriptApplication -TenantFilter 'contoso.com' -Properties $Properties -FilePath 'app.intunewin' -FileName 'app.intunewin' -UnencryptedSize 1024000 -EncryptionInfo $EncryptionInfo
34+ Add-CIPPW32ScriptApplication -TenantFilter 'contoso.com' -Properties $Properties
4535 #>
4636 [CmdletBinding ()]
4737 param (
4838 [Parameter (Mandatory = $true )]
4939 [string ]$TenantFilter ,
5040
5141 [Parameter (Mandatory = $true )]
52- [PSCustomObject ]$Properties ,
42+ [PSCustomObject ]$Properties
43+ )
5344
54- [Parameter (Mandatory = $true )]
55- [string ]$FilePath ,
45+ # Get the standard Chocolatey package location (relative to function app root)
46+ $IntuneWinFile = ' AddChocoApp\IntunePackage.intunewin'
47+ $ChocoXmlFile = ' AddChocoApp\Choco.App.xml'
5648
57- [Parameter (Mandatory = $true )]
58- [string ]$FileName ,
49+ if (-not (Test-Path $IntuneWinFile )) {
50+ throw " Chocolatey IntunePackage.intunewin not found at: $IntuneWinFile (Current directory: $PWD )"
51+ }
5952
60- [Parameter (Mandatory = $true )]
61- [int64 ]$UnencryptedSize ,
53+ if (-not (Test-Path $ChocoXmlFile )) {
54+ throw " Choco.App.xml not found at: $ChocoXmlFile (Current directory: $PWD )"
55+ }
6256
63- [Parameter (Mandatory = $true )]
64- [hashtable ]$EncryptionInfo
65- )
57+ # Parse the Choco XML to get encryption info
58+ [xml ]$ChocoXml = Get-Content $ChocoXmlFile
59+ $EncryptionInfo = @ {
60+ EncryptionKey = $ChocoXml.ApplicationInfo.EncryptionInfo.EncryptionKey
61+ MacKey = $ChocoXml.ApplicationInfo.EncryptionInfo.MacKey
62+ InitializationVector = $ChocoXml.ApplicationInfo.EncryptionInfo.InitializationVector
63+ Mac = $ChocoXml.ApplicationInfo.EncryptionInfo.Mac
64+ ProfileIdentifier = $ChocoXml.ApplicationInfo.EncryptionInfo.ProfileIdentifier
65+ FileDigest = $ChocoXml.ApplicationInfo.EncryptionInfo.FileDigest
66+ FileDigestAlgorithm = $ChocoXml.ApplicationInfo.EncryptionInfo.FileDigestAlgorithm
67+ }
68+
69+ $FileName = $ChocoXml.ApplicationInfo.FileName
70+ $UnencryptedSize = [int64 ]$ChocoXml.ApplicationInfo.UnencryptedContentSize
71+
72+ # Build detection rules
73+ if ($Properties.detectionPath ) {
74+ # Determine if this is a file or folder detection
75+ $DetectionRule = @ {
76+ ' @odata.type' = ' #microsoft.graph.win32LobAppFileSystemDetection'
77+ check32BitOn64System = if ($null -ne $Properties.check32BitOn64System ) { [bool ]$Properties.check32BitOn64System } else { $false }
78+ detectionType = if ($Properties.detectionType ) { $Properties.detectionType } else { ' exists' }
79+ }
80+
81+ if ($Properties.detectionFile ) {
82+ # File detection (path + file)
83+ $DetectionRule [' path' ] = $Properties.detectionPath
84+ $DetectionRule [' fileOrFolderName' ] = $Properties.detectionFile
85+ } else {
86+ # Folder/File detection (full path)
87+ # Split the path into directory and file/folder name
88+ $PathItem = Split-Path $Properties.detectionPath - Leaf
89+ $ParentPath = Split-Path $Properties.detectionPath - Parent
90+
91+ if ([string ]::IsNullOrEmpty($ParentPath )) {
92+ throw " Invalid detection path: $ ( $Properties.detectionPath ) . Must be a full path."
93+ }
6694
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 = @ (
95+ $DetectionRule [' path' ] = $ParentPath
96+ $DetectionRule [' fileOrFolderName' ] = $PathItem
97+ }
98+
99+ $DetectionRules = @ ($DetectionRule )
100+ } else {
101+ # Default detection: Check for a marker file in ProgramData
102+ $DetectionRules = @ (
103+ @ {
104+ ' @odata.type' = ' #microsoft.graph.win32LobAppFileSystemDetection'
105+ path = ' %ProgramData%\CIPPApps'
106+ fileOrFolderName = " $ ( $Properties.displayName -replace ' [^a-zA-Z0-9]' , ' _' ) .txt"
107+ check32BitOn64System = $false
108+ detectionType = ' exists'
109+ }
110+ )
111+ }
112+
113+ # Build the Win32 app body
114+ $AppBody = @ {
115+ ' @odata.type' = ' #microsoft.graph.win32LobApp'
116+ displayName = $Properties.displayName
117+ description = $Properties.description
118+ publisher = if ($Properties.publisher ) { $Properties.publisher } else { ' CIPP' }
119+ fileName = $FileName
120+ setupFilePath = ' N/A'
121+ installCommandLine = ' powershell.exe -ExecutionPolicy Bypass -File install.ps1'
122+ uninstallCommandLine = ' powershell.exe -ExecutionPolicy Bypass -File uninstall.ps1'
123+ minimumSupportedWindowsRelease = ' 1607'
124+ detectionRules = $DetectionRules
125+ returnCodes = @ (
77126 @ { returnCode = 0 ; type = ' success' }
78127 @ { returnCode = 1707 ; type = ' success' }
79128 @ { returnCode = 3010 ; type = ' softReboot' }
80129 @ { returnCode = 1641 ; type = ' hardReboot' }
81130 @ { returnCode = 1618 ; type = ' retry' }
82131 )
132+ installExperience = @ {
133+ ' @odata.type' = ' microsoft.graph.win32LobAppInstallExperience'
134+ runAsAccount = if ($Properties.runAsAccount ) { $Properties.runAsAccount } else { ' system' }
135+ deviceRestartBehavior = if ($Properties.deviceRestartBehavior ) { $Properties.deviceRestartBehavior } else { ' suppress' }
136+ }
83137 }
84138
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
139+ # Create the app first
94140 $Baseuri = ' https://graph.microsoft.com/beta/deviceAppManagement/mobileApps'
95- $NewApp = New-GraphPostRequest - Uri $Baseuri - Body ($intuneBody | ConvertTo-Json - Depth 10 ) - Type POST - tenantid $TenantFilter
141+ $NewApp = New-GraphPostRequest - Uri $Baseuri - Body ($AppBody | ConvertTo-Json - Depth 10 ) - Type POST - tenantid $TenantFilter
96142 Start-Sleep - Milliseconds 200
97143
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 }
144+ # Upload the Chocolatey intunewin content
145+ Add-CIPPWin32LobAppContent - AppId $NewApp.id - FilePath $IntuneWinFile - FileName $FileName - UnencryptedSize $UnencryptedSize - EncryptionInfo $EncryptionInfo - TenantFilter $TenantFilter
104146
147+ # Upload PowerShell scripts via the scripts endpoint (newer method)
105148 $InstallScriptId = $null
106149 $UninstallScriptId = $null
107150
@@ -110,8 +153,8 @@ function Add-CIPPW32ScriptApplication {
110153 $InstallScriptBody = @ {
111154 ' @odata.type' = ' #microsoft.graph.win32LobAppInstallPowerShellScript'
112155 displayName = ' install.ps1'
113- enforceSignatureCheck = $EnforceSignatureCheck
114- runAs32Bit = $RunAs32Bit
156+ enforceSignatureCheck = $false
157+ runAs32Bit = $false
115158 content = $InstallScriptContent
116159 } | ConvertTo-Json
117160
@@ -133,8 +176,8 @@ function Add-CIPPW32ScriptApplication {
133176 $UninstallScriptBody = @ {
134177 ' @odata.type' = ' #microsoft.graph.win32LobAppUninstallPowerShellScript'
135178 displayName = ' uninstall.ps1'
136- enforceSignatureCheck = $EnforceSignatureCheck
137- runAs32Bit = $RunAs32Bit
179+ enforceSignatureCheck = $false
180+ runAs32Bit = $false
138181 content = $UninstallScriptContent
139182 } | ConvertTo-Json
140183
@@ -153,8 +196,8 @@ function Add-CIPPW32ScriptApplication {
153196
154197 # Build final commit body with active script references
155198 $CommitBody = @ {
156- ' @odata.type' = ' #microsoft.graph.win32LobApp'
157- committedContentVersion = ' 1'
199+ ' @odata.type' = ' #microsoft.graph.win32LobApp'
200+ committedContentVersion = ' 1'
158201 }
159202
160203 if ($InstallScriptId ) {
@@ -165,22 +208,8 @@ function Add-CIPPW32ScriptApplication {
165208 $CommitBody [' activeUninstallScript' ] = @ { targetId = $UninstallScriptId }
166209 }
167210
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-
181211 # Commit the app with script references
182212 $null = New-GraphPostRequest - Uri " $Baseuri /$ ( $NewApp.id ) " - tenantid $TenantFilter - Body ($CommitBody | ConvertTo-Json - Depth 10 ) - Type PATCH
183213
184214 return $NewApp
185-
186215}
0 commit comments