|
| 1 | +--- |
| 2 | +title: Using Chocolatey to manage endpoint apps with Intune |
| 3 | +description: Intune can be very repetative when it comes to keeping certain apps up-to-date. Chocolatey can help take that workload off our backs. |
| 4 | +author: thackmaster |
| 5 | +date: 2025-04-25 |
| 6 | +last_modified_at: 2025-08-23 |
| 7 | +categories: [Microsoft] |
| 8 | +tags: [intune, cloud computing] |
| 9 | +image: |
| 10 | + path: https://images.unsplash.com/photo-1519389950473-47ba0277781c |
| 11 | + alt: Image from Marvin Meyer (@marvelous) via Unsplash |
| 12 | +--- |
| 13 | + |
| 14 | +Chocolatey is a package management tool built for Windows, similar to Brew for macOS or `apt` or `yum` for Linux. Intune applications, especially in larger environments or a smaller team managing many clients like an MSP, can be somewhat cumbersome in their management. |
| 15 | + |
| 16 | +## Step 1: Install Chocolatey |
| 17 | +### via MSI Installer |
| 18 | +Head to https://github.com/chocolatey/choco/releases to download the latest `.msi` file to deploy. |
| 19 | + |
| 20 | +Once acquired, head to Intune > Apps > All Apps. Click "Create". |
| 21 | + |
| 22 | +In the "App type" dropdown, select "Line-of-business app". |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +Title the app, fill in the description, and list the publisher. |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +Assign and deploy Chocolatey. That's it! |
| 31 | + |
| 32 | +### via Chocolatey script |
| 33 | +You can also opt to deploy the installer via a script. I would recommend installing it via a Remediation script, but you can also utilize Platform scripts since this only needs to be ran successfully once. |
| 34 | + |
| 35 | +> You must have your execution policy set to `Bypass` or `AllSigned` to run this. |
| 36 | +{: .prompt-tip} |
| 37 | + |
| 38 | +```powershell |
| 39 | +Set-ExecutionPolicy Bypass -Scope Process -Force; |
| 40 | +[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; |
| 41 | +iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) |
| 42 | +``` |
| 43 | + |
| 44 | +1. Go to [https://chocolatey.org/install](https://chocolatey.org/install) and select "Individual" for "Choose How to Install Chocolatey". |
| 45 | +2. Scroll down and copy the listed command. |
| 46 | +3. Save the copied command in a `.ps1` file. |
| 47 | +4. Go to Intune > Devices > Scripts and remediation. |
| 48 | +5. Create and deploy the script as you see fit. |
| 49 | + |
| 50 | +## Step 2: Write and wrap application installation commands to .intunewin file |
| 51 | +Now that Chocolatey is installed properly, let's install a package! Installing packages is extremely easy and run on the same concept of installing them on your own local machine. Each package will consist of only two items: an install script and an uninstall script. |
| 52 | + |
| 53 | +For an install script: |
| 54 | + |
| 55 | +```powershell |
| 56 | +$appname = "notepadplusplus" |
| 57 | +$localprograms = choco list |
| 58 | +
|
| 59 | +if ($localprograms -like "*$appname*") |
| 60 | +{ |
| 61 | + #choco upgrade $appname |
| 62 | +} |
| 63 | +else |
| 64 | +{ |
| 65 | + choco install $appname -y |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +I opted to not conduct any action here for upgrading the application if it already exists as I handle those separately and more frequently (see [step 4](#step-4-optional-write-and-deploy-automatic-updates-for-chocolatey-packages)). |
| 70 | + |
| 71 | +For an uninstall script: |
| 72 | + |
| 73 | +```powershell |
| 74 | +$appname = "notepadplusplus" |
| 75 | +
|
| 76 | +choco uninstall $appname -y --limit-output |
| 77 | +choco uninstall ($appname + ".install") -y --limit-output |
| 78 | +
|
| 79 | +$localprograms = choco list |
| 80 | +#Write-Output($localprograms) |
| 81 | +foreach($program in $localprograms) { |
| 82 | + if($program -like "*$appname*") { |
| 83 | + choco uninstall $appname -y --limit-output |
| 84 | + } |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +Save these two files in the same folder. Then, using the [IntuneWinWrapTool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool), wrap this folder. |
| 89 | + |
| 90 | + |
| 91 | + |
| 92 | +## Step 3: Deploy an application |
| 93 | +With your new `.intunewin` file, head to Intune > Apps > All apps. Select "Create", then "Windows app (Win32)". |
| 94 | + |
| 95 | + |
| 96 | + |
| 97 | +Fill out the application details as you normally would. Use `powershell.exe -executionpolicy bypass .\install.ps1` and `powershell.exe -executionpolicy bypass .\uninstall.ps1` for your install and uninstall commands. |
| 98 | + |
| 99 | +You can choose to look for the proper install location for an application in your detection rules or you can look at the Chocolatey folder. In this case, Notepad++ installs in the proper folder of `C:\Program Files\Notepad++`. It also appears from Chocolatey in the folder `C:\ProgramData\chocolatey\lib\notepadplusplus`. Either are totally valid ways to detect your application, it is up to you how you decide to do it. |
| 100 | + |
| 101 | +## Step 4 (optional): Write and Deploy automatic updates for Chocolatey packages |
| 102 | +After doing the above, it's a good idea to keep Chocolatey and all the packages with it up to date. There are myriad of ways to accomplish this; I'll be doing the broadest method, which is to upgrade anything that has an update ever. This is easy enough to undo in the future if a bad package comes out (i.e., delete the script and rewrap your application with the install command flag of `--version=123 --force`). |
| 103 | + |
| 104 | +The script below: |
| 105 | +- Creates a folder called "ChocoStartup" to store `update.ps1` and `choco_update.log` in, then determines the Windows Startup folder. |
| 106 | +- Creates `update.ps1` with a file that will automatically run `choco update all` on startup and log anything that comes back as needing an upgrade. |
| 107 | +- Creates a shortcut in the Windows Startup folder so that the script executes on startup. |
| 108 | + |
| 109 | +```powershell |
| 110 | +# Define paths |
| 111 | +$scriptFolder = "C:\ProgramData\ChocoStartup" |
| 112 | +$scriptPath = "$scriptFolder\update.ps1" |
| 113 | +$logPath = "$scriptFolder\choco_update.log" |
| 114 | +$startupShortcut = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\ChocoUpdate.lnk" |
| 115 | +
|
| 116 | +# Create folder for script and log |
| 117 | +New-Item -ItemType Directory -Path $scriptFolder -Force | Out-Null |
| 118 | +
|
| 119 | +# Write the update script with filtered logging |
| 120 | +$scriptContent = @" |
| 121 | +# Setup |
| 122 | +`$logPath = "C:\ProgramData\ChocoStartup\choco_update.log" |
| 123 | +`$tempLog = "C:\ProgramData\ChocoStartup\choco_raw_output.tmp" |
| 124 | +
|
| 125 | +# Create folder if missing |
| 126 | +if (!(Test-Path "C:\ProgramData\ChocoStartup")) { |
| 127 | + New-Item -Path "C:\ProgramData\ChocoStartup" -ItemType Directory -Force |
| 128 | +} |
| 129 | +
|
| 130 | +# Create temp folder |
| 131 | +Set-Content -Path `$tempLog -Value `$null |
| 132 | +
|
| 133 | +# Write header |
| 134 | +`$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " - Starting choco upgrade all" | Out-File -Append `$logPath |
| 135 | +
|
| 136 | +# Run Chocolatey and capture all output |
| 137 | +`$cmdLine = `"choco upgrade all -y >> ``"`$tempLog``" 2>&1`" |
| 138 | +cmd /c `$cmdLine |
| 139 | +
|
| 140 | +# Track output |
| 141 | +`$lines = Get-Content `$tempLog |
| 142 | +`$filteredOutput = @() |
| 143 | +`$upgradeList = @() |
| 144 | +`$inUpgradeBlock = `$false |
| 145 | +
|
| 146 | +foreach (`$line in `$lines) { |
| 147 | + `$trimmed = `$line.Trim() |
| 148 | +
|
| 149 | + # Collect the package names listed under "Upgrading the following packages:" |
| 150 | + if (`$trimmed -match '^Upgrading the following packages:') { |
| 151 | + `$inUpgradeBlock = `$true |
| 152 | + continue |
| 153 | + } |
| 154 | +
|
| 155 | + if (`$inUpgradeBlock) { |
| 156 | + if (`$trimmed -match '^By upgrading') { |
| 157 | + `$inUpgradeBlock = `$false |
| 158 | + continue |
| 159 | + } |
| 160 | + if (`$trimmed -ne "") { |
| 161 | + `$upgradeList += `$trimmed |
| 162 | + } |
| 163 | + continue |
| 164 | + } |
| 165 | +
|
| 166 | + # Match the actual version upgrade notification |
| 167 | + if (`$trimmed -match '^You have .+ v[\d\.]+ installed\. Version [\d\.]+ is available') { |
| 168 | + `$filteredOutput += `$trimmed |
| 169 | + } |
| 170 | +
|
| 171 | + # Add any successful confirmation lines just in case |
| 172 | + if (`$trimmed -match 'Successfully installed|was upgraded') { |
| 173 | + `$filteredOutput += `$trimmed |
| 174 | + } |
| 175 | +} |
| 176 | +
|
| 177 | +# Add upgrade header if real packages are listed (and not just 'all') |
| 178 | +if (`$upgradeList.Count -gt 0 -and -not (`$upgradeList -eq 'all')) { |
| 179 | + `$filteredOutput = @("Upgrading the following packages:") + `$upgradeList + `$filteredOutput |
| 180 | +} |
| 181 | +
|
| 182 | +# Write filtered results |
| 183 | +if (`$filteredOutput.Count -gt 0) { |
| 184 | + `$filteredOutput | Out-File -Append `$logPath |
| 185 | +} else { |
| 186 | + "No packages needed upgrading." | Out-File -Append `$logPath |
| 187 | +} |
| 188 | +# Write footer |
| 189 | +`$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " - Finished choco upgrade all" | Out-File -Append `$logPath |
| 190 | +
|
| 191 | +# Clean up tmp |
| 192 | +Remove-Item `$tempLog -Force -ErrorAction SilentlyContinue |
| 193 | +"@ |
| 194 | +
|
| 195 | +# Write the script to disk |
| 196 | +Set-Content -Path $scriptPath -Value $scriptContent -Force |
| 197 | +
|
| 198 | +# Create shortcut in Startup folder |
| 199 | +$WshShell = New-Object -ComObject WScript.Shell |
| 200 | +$shortcut = $WshShell.CreateShortcut($startupShortcut) |
| 201 | +$shortcut.TargetPath = "powershell.exe" |
| 202 | +$shortcut.Arguments = "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`"" |
| 203 | +$shortcut.WorkingDirectory = $scriptFolder |
| 204 | +$shortcut.Save() |
| 205 | +``` |
| 206 | + |
| 207 | +And of course, a detection script for deployment as a Remedition within Intune. |
| 208 | + |
| 209 | +```powershell |
| 210 | +Test-Path "C:\ProgramData\ChocoStartup\update.ps1" -and Test-Path "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\ChocoUpdate.lnk" |
| 211 | +``` |
| 212 | + |
| 213 | +## References |
| 214 | +- [https://techcommunity.microsoft.com/discussions/microsoft-intune/simple-method-to-run-logon--recurring-scripts-in-intune/686861](https://techcommunity.microsoft.com/discussions/microsoft-intune/simple-method-to-run-logon--recurring-scripts-in-intune/686861) |
| 215 | +- [https://docs.chocolatey.org/en-us/choco/setup/#install-using-the-msi](https://docs.chocolatey.org/en-us/choco/setup/#install-using-the-msi) |
0 commit comments