├── .gitattributes ├── .github └── workflows │ └── main.yml ├── Content ├── Module │ ├── Private │ │ ├── Add-Hash.ps1 │ │ ├── ConvertTo-Hashtable.ps1 │ │ ├── Expand-VariablesInString.ps1 │ │ ├── Format-UninstallString.ps1 │ │ ├── Get-BloatwareWin32Instructions.ps1 │ │ ├── Get-RegistryEntry.ps1 │ │ ├── Remove-BloatwareWin32Custom.ps1 │ │ ├── Remove-BloatwareWin32InstallShield.ps1 │ │ ├── Remove-BloatwareWin32Msi.ps1 │ │ ├── Remove-BloatwareWin32Passthrough.ps1 │ │ └── Test-VariableName.ps1 │ ├── Public │ │ ├── Remove-BloatwareAllAppxByPublisher.ps1 │ │ ├── Remove-BloatwareAllAppxProvisionedByPublisher.ps1 │ │ ├── Remove-BloatwareAppx.ps1 │ │ ├── Remove-BloatwareAppxProvisioned.ps1 │ │ ├── Remove-BloatwareWin32.ps1 │ │ └── Uninstall-Bloatware.ps1 │ ├── UninstallBloatware.psd1 │ ├── UninstallBloatware.psm1 │ └── Win32Instructions │ │ ├── HP Client Security Manager.json │ │ ├── HP Collaboration Keyboard For Cisco UCC.json │ │ ├── HP Collaboration Keyboard for Skype for Business.json │ │ ├── HP Connection Optimizer.json │ │ ├── HP Device Access Manager.json │ │ ├── HP Documentation.json │ │ ├── HP JumpStart Apps.json │ │ ├── HP JumpStart Bridge.json │ │ ├── HP JumpStart Launch.json │ │ ├── HP Notifications.json │ │ ├── HP Recovery Manager.json │ │ ├── HP Security Update Service.json │ │ ├── HP SoftPaq Download Manager.json │ │ ├── HP Software Setup.json │ │ ├── HP Support Assistant.json │ │ ├── HP Support Solutions Framework.json │ │ ├── HP Sure Click.json │ │ ├── HP Sure Connect.json │ │ ├── HP Sure Recover.json │ │ ├── HP Sure Run.json │ │ ├── HP Sure Sense Installer.json │ │ ├── HP System Software Manager.json │ │ ├── HP Velocity.json │ │ ├── HP Wolf Security.json │ │ ├── HP WorkWise.json │ │ ├── IPMPLUS.json │ │ └── ISS-HPConnectionOptimizer.iss └── UninstallBloatwareSample.ps1 ├── InTune.md ├── LICENSE ├── New-InTuneWinPackage.ps1 ├── Output ├── Content.intunewin ├── Content.intunewin.json └── Content.portal.intunewin └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.exe filter=lfs diff=lfs merge=lfs -text 2 | *.msi filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ main, dev ] 10 | pull_request: 11 | branches: [ main, dev ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | - name: Run PSScriptAnalyzer 29 | uses: microsoft/psscriptanalyzer-action@v1.0 30 | #with: 31 | # Specifies the path to the scripts or module to be analyzed. Wildcard characters are supported. 32 | #path: # default is .\ 33 | # Uses only the custom rules defined in the specified paths to the analysis. To still use the built-in rules, add the -IncludeDefaultRules switch. 34 | #customRulePath: # optional 35 | # Adds rules defined in subdirectories of the CustomRulePath location. 36 | #recurseCustomRulePath: # optional 37 | # Omits the specified rules from the Script Analyzer test. Wildcard characters are supported. 38 | #excludeRule: # optional 39 | # Invoke default rules along with Custom rules. 40 | #includeDefaultRules: # optional 41 | # Runs only the specified rules in the Script Analyzer test. 42 | #includeRule: # optional 43 | # After running Script Analyzer with all rules, this parameter selects rule violations with the specified severity. 44 | #severity: # optional 45 | # Runs Script Analyzer on the files in the Path directory and all subdirectories recursively. 46 | #recurse: # optional 47 | # Returns rules that are suppressed, instead of analyzing the files in the path. 48 | #suppressedOnly: # optional 49 | # Fixes certain warnings which contain a fix in their DiagnosticRecord. 50 | #fix: # optional 51 | # Exits PowerShell and returns an exit code equal to the number of error records. 52 | #enableExit: # optional 53 | # File path that contains user profile or hash table for ScriptAnalyzer. 54 | #settings: # optional 55 | # Specifies where the path for the sarif file 56 | #output: # default is results.sarif 57 | # Exclude specific files from the sarif results. Uses regex pattern. 58 | #ignorePattern: # optional 59 | -------------------------------------------------------------------------------- /Content/Module/Private/Add-Hash.ps1: -------------------------------------------------------------------------------- 1 | function Add-Hash { 2 | <# 3 | .SYNOPSIS 4 | Adds keys and values from one hashtable to another and returns the result. 5 | 6 | .DESCRIPTION 7 | Adds keys and values from one hashtable to another and returns the result. Looks for KeyName in FromHashtable and adds to ToHashtable 8 | 9 | .PARAMETER FromHashtable 10 | The hashtable that has the keys to add to ToHashtable. 11 | 12 | .PARAMETER ToHashtable 13 | The hashtable that the keys will be added to. 14 | 15 | .PARAMETER KeyName 16 | String array of the keys to look for. 17 | 18 | .EXAMPLE 19 | Add-Hash -FromHashtable $fromHash -ToHashtable $toHash -KeyName @('MyKey1', 'MyKey2') 20 | 21 | .INPUTS 22 | Two hashtables and a string array. 23 | 24 | .OUTPUTS 25 | Hashtable. 26 | 27 | .NOTES 28 | Original Author: Sean Sauve 29 | #> 30 | [CmdletBinding()] 31 | [OutputType([hashtable])] 32 | param ( 33 | [Parameter(Mandatory)] 34 | [hashtable]$FromHashtable, 35 | 36 | [Parameter(Mandatory)] 37 | [hashtable]$ToHashtable, 38 | 39 | [Parameter(Mandatory)] 40 | [string[]]$KeyName 41 | ) 42 | 43 | foreach($singleKeyName in $KeyName) { 44 | Write-Verbose "Add-Hash: Looking for key (`$singleKeyName): $singleKeyName" 45 | if ($FromHashtable.ContainsKey($singleKeyName)) { 46 | Write-Verbose "Add-Hash: Adding key (from `$FromHashTable to `$ToHashTable): $singleKeyName = $($FromHashtable[$singleKeyName])" 47 | $ToHashtable += @{$singleKeyName = $FromHashtable[$singleKeyName]} 48 | } 49 | else { 50 | Write-Verbose "Add-Hash: Could not find key (`$singleKeyName in hashtable `$FromHashtable): $singleKeyName" 51 | } 52 | } 53 | 54 | $ToHashtable 55 | } 56 | -------------------------------------------------------------------------------- /Content/Module/Private/ConvertTo-Hashtable.ps1: -------------------------------------------------------------------------------- 1 | function ConvertTo-Hashtable { 2 | <# 3 | .SYNOPSIS 4 | Converts an object to a hashtable 5 | 6 | .DESCRIPTION 7 | Converts an object to a hashtable 8 | 9 | .PARAMETER InputObject 10 | The object you want to convert to a hashtable 11 | 12 | .EXAMPLE 13 | Get-Content -Path './myfile.json' -Raw | ConvertFrom-Json | ConvertTo-HashTable 14 | 15 | .INPUTS 16 | InputObject 17 | 18 | .OUTPUTS 19 | Hashtable. 20 | 21 | .NOTES 22 | Original Author: Adam Bertram 23 | Original Link: https://4sysops.com/archives/convert-json-to-a-powershell-hash-table 24 | Updated by Sean Sauve 25 | #> 26 | [CmdletBinding()] 27 | [OutputType([hashtable])] 28 | param ( 29 | [Parameter(ValueFromPipeline)] 30 | $InputObject 31 | ) 32 | 33 | process { 34 | 35 | if ($null -eq $InputObject) { 36 | return $null 37 | } 38 | 39 | if ($InputObject -is [system.collections.ienumerable] -and $InputObject -isnot [string]) { 40 | Write-Verbose "ConvertTo-Hashtable: `$InputObject is an array or collection. Convert each to hash table when applicable." 41 | $collection = @( 42 | foreach ($object in $InputObject) { 43 | ConvertTo-Hashtable -InputObject $object 44 | } 45 | ) 46 | $collection 47 | } 48 | elseif ($InputObject -is [psobject]) { 49 | Write-Verbose "ConvertTo-Hashtable: `$InputObject has properties that need enumeration." 50 | $hashtable = @{} 51 | foreach ($property in $InputObject.PSObject.Properties) { 52 | $hashtable[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value 53 | } 54 | $hashtable 55 | } 56 | else { 57 | Write-Verbose "ConvertTo-Hashtable: `$InputObject is already a hashtable." 58 | $InputObject 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Content/Module/Private/Expand-VariablesInString.ps1: -------------------------------------------------------------------------------- 1 | function Expand-VariablesInString { 2 | <# 3 | .SYNOPSIS 4 | Reads a string and looks for environment variable names to replace with their values. 5 | 6 | .DESCRIPTION 7 | Reads a string and looks for environment variable names to replace with their values. 8 | Supports the use of $PSScriptRoot with the parameter PSScriptRootDirectory. 9 | See the parameter help for PSScriptRootDirectory for the implementation details. 10 | 11 | .PARAMETER PSScriptRootDirectory 12 | Replaces occurances of $PSScriptRoot with a custom directory that you specify. 13 | 14 | .PARAMETER VariableNames 15 | Specify to restrict the variables that will be searched for. Do not include the $ in variable names 16 | when specifying it in VariableNames. 17 | 18 | This function will search for each variable using up to three possible ways it could appear: 19 | $VariableName 20 | $($VariableName) 21 | ${VariableName} 22 | 23 | Scope modifiers and namespaces other than env: are not yet supported. 24 | 25 | Other than those in the Env: namespace, variable names that require ${} are not yet supported. 26 | 27 | Default value is: 28 | env:ProgramData 29 | env:ProgramFiles 30 | env:SystemDrive 31 | env:ProgramFiles(x86) 32 | env:CommonProgramW6432 33 | env:CommonProgramFiles(x86) 34 | env:DriverData 35 | env:CommonProgramFiles 36 | env:TEMP 37 | env:TMP 38 | env:ProgramW6432 39 | env:windir 40 | PSScriptRoot 41 | 42 | Does accepts $null or an empty string. 43 | 44 | .PARAMETER AllowSemicolonInValues 45 | Specify this parameter to allow the use of the semicolon in variable values. When set to false and a 46 | semicolon is found in a variable's value that is being expanded, this function will throw an error. 47 | 48 | .PARAMETER ToRawJson 49 | Formats the values expanded by this function to include escape characters needed for raw JSON data. 50 | 51 | .EXAMPLE 52 | Expand-VariablesInString -String "$ProgramData\Microsoft" 53 | 54 | .EXAMPLE 55 | Expand-VariablesInString -String "$PSScriptRoot\file.txt" -PSScriptRootDirectory "C:\Temp" 56 | 57 | .EXAMPLE 58 | Expand-VariablesInString -String "$ProgramData\Microsoft" -ToRawJson 59 | 60 | .EXAMPLE 61 | Expand-VariablesInString -String "$ProgramData\Microsoft" -VariableNames @('env:ProgramData') 62 | 63 | .INPUTS 64 | String 65 | 66 | .OUTPUTS 67 | String 68 | 69 | .NOTES 70 | Original Author: Sean Sauve 71 | #> 72 | [CmdletBinding()] 73 | [OutputType([string])] 74 | param ( 75 | [Parameter(ValueFromPipeline, Mandatory, Position=0)] 76 | [AllowEmptyString()] 77 | [AllowNull()] 78 | [string[]] 79 | $String, 80 | 81 | [Parameter()] 82 | [string[]] 83 | $VariableNames = @( 84 | 'env:ProgramData' 85 | 'env:ProgramFiles' 86 | 'env:SystemDrive' 87 | 'env:ProgramFiles(x86)' 88 | 'env:CommonProgramW6432' 89 | 'env:CommonProgramFiles(x86)' 90 | 'env:DriverData' 91 | 'env:CommonProgramFiles' 92 | 'env:TEMP' 93 | 'env:TMP' 94 | 'env:ProgramW6432' 95 | 'env:windir' 96 | 'PSScriptRoot' 97 | ), 98 | 99 | [Parameter()] 100 | [ValidateNotNullOrEmpty()] 101 | [string]$PSScriptRootDirectory, 102 | 103 | [Parameter()] 104 | [switch]$ToRawJson, 105 | 106 | [Parameter()] 107 | [switch]$AllowSemicolonInValues 108 | ) 109 | 110 | begin { 111 | $jsonEscChars = @{ 112 | "\" = '\\' 113 | "`b" = '\b' 114 | "`f" = '\f' 115 | "`n" = '\n' 116 | "`r" = '\r' 117 | "`t" = '\t' 118 | '"' = '\"' 119 | } 120 | 121 | if(($null -eq $VariableNames) -or ('' -eq $VariableNames)) { 122 | Write-Verbose "Expand-VariablesInString: VariableNames is null or empty" 123 | return 124 | } 125 | 126 | $variables = @{} 127 | foreach($variable in $VariableNames) { 128 | if($null -eq $variable -or '' -eq $variable) { 129 | Write-Error 'Expand-VariablesInString: null or empty variable name.' 130 | } 131 | if($variable -like 'env:*') { 132 | $variables[$variable] = [Environment]::GetEnvironmentVariable($variable.Replace('env:','')) 133 | } 134 | elseif($variable.IndexOf(':') -ne -1) { 135 | Write-Error 'Expand-VariablesInString: scope modifiers and namespaces other than env: are not yet supported.' 136 | } 137 | elseif(-not (Test-VariableName -Name $variable)) { 138 | Write-Error 'Expand-VariablesInString: variable names (other than env:*) that require ${} are not yet supported.' 139 | } 140 | elseif(-not (Test-Path -Path "Variable:\$variable")) { 141 | Write-Verbose "Expand-VariablesInString: variable not found" 142 | $variables[$variable] = $null 143 | } 144 | elseif($variable -eq 'PSScriptRoot') { 145 | if($PSBoundParameters.ContainsKey('PSScriptRootDirectory')) { 146 | Write-Verbose "Expand-VariablesInString: Using PSScriptRootDirectory $PSScriptRootDirectory" 147 | $variables['PSScriptRoot'] = $PSScriptRootDirectory 148 | } else { 149 | Write-Warning "Expand-VariablesInString: PSScriptRootDirectory is not specified" 150 | $variables['PSScriptRoot'] = $null 151 | } 152 | } 153 | else { 154 | $variables[$variable] = Get-Variable -Name $variable -ValueOnly 155 | } 156 | } 157 | 158 | $formatedVariables = @{} 159 | :foreachVariable foreach($variable in $($variables.Keys)){ 160 | if(($null -eq $variables[$variable]) -or ('' -eq $variables[$variable])) { 161 | continue foreachVariable 162 | } 163 | $value = $variables[$variable] 164 | if($ToRawJson) { 165 | foreach($escChar in $jsonEscChars.GetEnumerator()) { 166 | $value = $value.Replace($escChar.Name, $escChar.Value) 167 | } 168 | } 169 | $formatedVariables["`${$variable}"] = $value 170 | $variableNameDoesntRequireBraces = Test-VariableName -Name $variable 171 | if($variableNameDoesntRequireBraces) { 172 | $formatedVariables["`$(`$$variable)"] = $value 173 | $formatedVariables["`$$variable"] = $value 174 | } 175 | } 176 | 177 | $sortedVariables = $formatedVariables.GetEnumerator() | Sort-Object {$_.Name.Length} -Descending 178 | } 179 | 180 | process { 181 | foreach($singleString in $String) { 182 | if(($null -eq $VariableNames) -or ('' -eq $VariableNames)) { 183 | Write-Verbose "Expand-VariablesInString: VariableNames is null or empty" 184 | $singleString 185 | return 186 | } 187 | if(($null -eq $singleString) -or ('' -eq $singleString)) { 188 | Write-Verbose "Expand-VariablesInString: string is null or empty." 189 | $singleString 190 | return 191 | } 192 | if($singleString.IndexOf("$") -eq -1) { 193 | Write-Verbose "Expand-VariablesInString: string does not contain `$." 194 | $singleString 195 | return 196 | } 197 | foreach($variable in $sortedVariables) { 198 | if($singleString.IndexOf($variable.Name) -ne -1) { 199 | Write-Verbose "Expand-VariablesInString: found $($variable.Name), replacing with $($variable.Value)" 200 | if($variable.Value.IndexOf(';') -ne -1) { 201 | Write-Error ("Expand-VariablesInString: $($variable.Name) contains a semicolon" + 202 | " and AllowSemicolonInValues is not true. Value of $($variable.Name): $($variable.Value)") 203 | return 204 | } 205 | else { 206 | $singleString = $singleString.Replace($variable.Name, $variable.Value) 207 | } 208 | } 209 | } 210 | $singleString 211 | } 212 | } 213 | 214 | end { 215 | 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Content/Module/Private/Format-UninstallString.ps1: -------------------------------------------------------------------------------- 1 | function Format-UninstallString { 2 | <# 3 | .SYNOPSIS 4 | Wraps the path of an executable file in quotes. 5 | 6 | .DESCRIPTION 7 | Wraps the path of an executable file in quotes. 8 | 9 | .PARAMETER UninstallString 10 | A string that could have '.exe' in it. 11 | 12 | .EXAMPLE 13 | Format-UninstallString -UninstallString "C:\Program Files\HP\HP Velocity\Uninstall.exe -s -fixyourmessHP" 14 | 15 | .INPUTS 16 | String 17 | 18 | .OUTPUTS 19 | String 20 | 21 | .NOTES 22 | Original Author: Sean Sauve 23 | #> 24 | [CmdletBinding()] 25 | [OutputType([string])] 26 | param ( 27 | [Parameter(Mandatory)] 28 | [string]$UninstallString 29 | ) 30 | 31 | $exePosition = $UninstallString.IndexOf('.exe') 32 | $quotePosition = $UninstallString.IndexOf('"') 33 | 34 | if (($exePosition -ne -1) -and (($quotePosition -eq -1) -or ($quotePosition -gt $exePosition))){ 35 | Write-Verbose "Format-UninstallString: no quotation mark or first quotation mark is after '.exe'." 36 | Write-Verbose "wraping the string in quotes from the beginning of the string to the end of '.exe'" 37 | $output = '"' + $UninstallString.Substring(0, $exePosition + 4) + '"' + $UninstallString.Substring($exePosition + 4) 38 | $output 39 | } 40 | else { 41 | $UninstallString 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Content/Module/Private/Get-BloatwareWin32Instructions.ps1: -------------------------------------------------------------------------------- 1 | function Get-BloatwareWin32Instructions { 2 | [OutputType([hashtable[]])] 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter(Mandatory)] 6 | [ValidateNotNullOrEmpty()] 7 | [string] 8 | $Name, 9 | 10 | [Parameter()] 11 | [ValidateNotNullOrEmpty()] 12 | [string[]] 13 | $CustomDirectory, 14 | 15 | [Parameter()] 16 | [string[]] 17 | $InstructionVariableNames 18 | ) 19 | 20 | if ($PSBoundParameters.ContainsKey('CustomDirectory')) { 21 | [string[]]$directories = $CustomDirectory 22 | } 23 | else { 24 | [string[]]$directories = @() 25 | } 26 | $directories += @("$PSScriptRoot\..\Win32Instructions") 27 | 28 | $foundDirectory = '' 29 | $foundName = '' 30 | ForEach($directory in $directories) { 31 | if(Test-Path -Path "$directory\$Name.json") { 32 | Write-Verbose "Get-BloatwareWin32Instructions: found instructions for $Name at $directory\$Name.json" 33 | $foundDirectory = $directory 34 | $foundName = "$Name.json" 35 | break 36 | } 37 | } 38 | 39 | $defaultSuccessExitCodes = @(0, 1707, 3010, 1641, 1605) 40 | if('' -eq $foundName) { 41 | Write-Verbose "Get-BloatwareWin32Instructions: instructions file for $Name not found. Using defaults." 42 | $instructions = @{ 43 | 'Name' = $Name 44 | 'RequiresDevelopment' = $true 45 | 'SuccessExitCodes' = $defaultSuccessExitCodes 46 | } 47 | } 48 | else { 49 | Write-Verbose "Get-BloatwareWin32Instructions: reading instructions file." 50 | 51 | $expandVariablesParams = @{ 52 | 'PSScriptRootDirectory' = $foundDirectory 53 | 'ToRawJson' = $true 54 | } 55 | if($PSBoundParameters.ContainsKey('InstructionVariableNames')) { 56 | $expandVariablesParams['VariableNames'] = $InstructionVariableNames 57 | } 58 | 59 | $content = (Get-Content -Path "$foundDirectory\$foundName" -Raw | 60 | Expand-VariablesInString @expandVariablesParams) 61 | 62 | $instructions = ($content | ConvertFrom-Json | ConvertTo-HashTable -Verbose:$false) 63 | 64 | if(-not $instructions.ContainsKey('SuccessExitCodes')) { 65 | Write-Verbose ("Get-BloatwareWin32Instructions: " + 66 | "SucessExitCodes not contained in instructions file. Using defaults") 67 | $instructions['SuccessExitCodes'] = @(0, 1707, 3010, 1641) 68 | } 69 | } 70 | 71 | $instructions 72 | } 73 | -------------------------------------------------------------------------------- /Content/Module/Private/Get-RegistryEntry.ps1: -------------------------------------------------------------------------------- 1 | function Get-RegistryEntry { 2 | [CmdletBinding(PositionalBinding = $false)] 3 | param( 4 | [Parameter(Mandatory)] 5 | [ValidateNotNullOrEmpty()] 6 | [string]$Name 7 | ) 8 | 9 | $regPaths = @( 10 | 'hklm:\software\microsoft\windows\currentversion\uninstall', 11 | 'hklm:\software\wow6432node\microsoft\windows\currentversion\uninstall' 12 | ) 13 | $entries = (Get-ChildItem -Path $regPaths | Get-ItemProperty | Where-Object DisplayName -eq $Name) 14 | $entries 15 | } 16 | -------------------------------------------------------------------------------- /Content/Module/Private/Remove-BloatwareWin32Custom.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareWin32Custom { 2 | [CmdletBinding( 3 | DefaultParameterSetName = 'Default', 4 | SupportsShouldProcess, 5 | PositionalBinding = $false 6 | )] 7 | param ( 8 | [Parameter(Mandatory)] 9 | [ValidateNotNullOrEmpty()] 10 | [string] 11 | $Name, 12 | 13 | [Parameter(Mandatory)] 14 | [ValidateNotNullOrEmpty()] 15 | [string[]] 16 | $CustomPaths, 17 | 18 | [Parameter()] 19 | [ValidateNotNullOrEmpty()] 20 | [string] 21 | $CustomArguments, 22 | 23 | [Parameter(ParameterSetName = 'MissingPathEqualsSuccess')] 24 | [Parameter(ParameterSetName = 'MissingPathEqualsPassthrough', Mandatory)] 25 | [Parameter(ParameterSetName = 'MissingPathEqualsMsi', Mandatory)] 26 | [Parameter(ParameterSetName = 'ForcingUninstall')] 27 | [ValidateNotNullOrEmpty()] 28 | $RegistryEntries, 29 | 30 | [Parameter()] 31 | [ValidateNotNullOrEmpty()] 32 | [string] 33 | $LogDirectory, 34 | 35 | [Parameter(ParameterSetName = 'MissingPathEqualsPassthrough')] 36 | [Parameter(ParameterSetName = 'MissingPathEqualsMsi')] 37 | [ValidateNotNullOrEmpty()] 38 | [string] 39 | $CustomSuffix, 40 | 41 | [ValidateNotNullOrEmpty()] 42 | [int64[]] 43 | $SuccessExitCodes = @(0, 1707, 3010, 1641), 44 | 45 | [Parameter(ParameterSetName = 'ForcingUninstall')] 46 | [Parameter(ParameterSetName = 'MissingPathEqualsSuccess', Mandatory)] 47 | [switch] 48 | $MissingPathEqualsSuccess, 49 | 50 | [Parameter(ParameterSetName = 'ForcingUninstall')] 51 | [Parameter(ParameterSetName = 'MissingPathEqualsPassthrough', Mandatory)] 52 | [switch] 53 | $MissingPathEqualsPassthrough, 54 | 55 | [Parameter(ParameterSetName = 'ForcingUninstall')] 56 | [Parameter(ParameterSetName = 'MissingPathEqualsMsi', Mandatory)] 57 | [switch] 58 | $MissingPathEqualsMsi, 59 | 60 | [Parameter(ParameterSetName = 'ForcingUninstall', Mandatory)] 61 | [switch] 62 | $ForcingUninstall 63 | ) 64 | 65 | $customPath = $null 66 | foreach($customPathTest in $CustomPaths) { 67 | if (Test-Path $customPathTest) { 68 | Write-Verbose "Remove-BloatwareWin32Custom: found path $customPathTest" 69 | $customPath = $customPathTest 70 | } 71 | } 72 | 73 | if ($null -eq $customPath) { 74 | if ($MissingPathEqualsSuccess) { 75 | Write-Warning "CustomPaths not found when uninstalling $Name but MissingPathEqualsSuccess is true" 76 | return 77 | } 78 | elseif ($ForcingUninstall) { 79 | Write-Host "$Name not installed." 80 | return 81 | } 82 | elseif ($MissingPathEqualsMsi) { 83 | Write-Warning "CustomPaths not found when uninstalling $Name, attempting msi" 84 | $params = @{'Name' = $Name} 85 | $params = Add-Hash -FromHashtable $PSBoundParameters -ToHashtable $params -KeyName @( 86 | 'RegistryEntries' 87 | 'LogDirectory' 88 | 'WarnOnMissingInstallStringEveryTime' 89 | 'CustomSuffix' 90 | 'SuccessExitCodes' 91 | ) 92 | Remove-BloatwareWin32Msi @params 93 | return 94 | } 95 | elseif ($MissingPathEqualsPassthrough) { 96 | Write-Warning "CustomPaths not found when uninstalling $Name, attempting passthrough" 97 | $params = @{'Name' = $Name} 98 | $params = Add-Hash -FromHashtable $PSBoundParameters -ToHashtable $params -KeyName @( 99 | 'RegistryEntries' 100 | 'SuccessExitCodes' 101 | 'MissingPathEqualsSuccess' 102 | 'CustomSuffix' 103 | ) 104 | #Logs not implemented 105 | Remove-BloatwareWin32Passthrough @params 106 | return 107 | } 108 | else { 109 | Write-Error "CustomPaths not found when uninstalling $Name" -ErrorAction 'Stop' 110 | return 111 | } 112 | } 113 | 114 | $uninstall = "`"$CustomPath`"" 115 | if ($PSBoundParameters.ContainsKey('CustomArguments')) { 116 | $uninstall = "$uninstall $CustomArguments" 117 | } 118 | Write-Host "`tUninstalling application with command '$uninstall'" 119 | if ($PSCmdlet.ShouldProcess("$Name", 'Uninstall')) { 120 | & cmd.exe /c $uninstall | Out-Host 121 | if ($LastExitCode -in $SuccessExitCodes) { 122 | Write-Host "`tExit Code: $LastExitCode" 123 | Write-Host "`tFinished uninstalling application $Name" 124 | } 125 | else { 126 | Write-Error "Exit code $LastExitCode uninstalling $Name" -ErrorAction 'Stop' 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Content/Module/Private/Remove-BloatwareWin32InstallShield.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareWin32InstallShield { 2 | [CmdletBinding( 3 | SupportsShouldProcess, 4 | PositionalBinding = $false 5 | )] 6 | param ( 7 | [Parameter(Mandatory)] 8 | [ValidateNotNullOrEmpty()] 9 | [string] 10 | $Name, 11 | 12 | [Parameter(Mandatory)] 13 | [ValidateNotNullOrEmpty()] 14 | $RegistryEntries, 15 | 16 | [Parameter()] 17 | [ValidateNotNullOrEmpty()] 18 | [string] 19 | $LogDirectory, 20 | 21 | [Parameter(Mandatory)] 22 | [ValidateNotNullOrEmpty()] 23 | [string] 24 | $ISSTemplate, 25 | 26 | [Parameter()] 27 | [switch] 28 | $ReplaceGUID, 29 | 30 | [Parameter()] 31 | [switch] 32 | $ReplaceVersion, 33 | 34 | [Parameter()] 35 | [ValidateNotNullOrEmpty()] 36 | [int64[]] 37 | $SuccessExitCodes = @(0, 1707, 3010, 1641) 38 | ) 39 | 40 | $uninstallStringCount = 0 41 | 42 | if ($PSBoundParameters.ContainsKey('LogDirectory')) { 43 | $tempDirectory = $LogDirectory 44 | } 45 | else { 46 | $tempDirectory = $PSScriptRoot 47 | } 48 | 49 | if($registryEntries.Count -gt 1) { 50 | if(($registryEntries.UninstallString | Select-Object -Unique).Count -eq 1) { 51 | $registryEntries | Select-Object -First 1 52 | } 53 | } 54 | 55 | :foreachRegistryEntry foreach ($registryEntry in $RegistryEntries) { 56 | $uninstall = $registryEntry.UninstallString 57 | if(($null -eq $uninstall) -or ('' -eq $uninstall)) { 58 | Write-Warning "`tRegistry entry UninstallString is null or empty" 59 | continue foreachRegistryEntry 60 | } 61 | $uninstall = Format-UninstallString -UninstallString $uninstall 62 | if($uninstall.IndexOf(".exe`"") -eq -1) { 63 | Write-Warning "`tRegistry entry UninstallString ($uninstall) could not be parsed" 64 | continue foreachRegistryEntry 65 | } 66 | 67 | $issContent = Get-Content $ISSTemplate 68 | if ($ReplaceGUID) { 69 | $issContent = $issContent.Replace('$GUID', $registryEntry.ProductGuid) 70 | } 71 | if ($ReplaceVersion) { 72 | $issContent = $issContent.Replace('$VERSION', $registryEntry.DisplayVersion) 73 | } 74 | 75 | $issFile = "$tempDirectory\ISSFile-$($Name.Replace(' ', '')).iss" 76 | $issContent | Out-File $issFile 77 | 78 | $setup = $uninstall.Substring(0, $uninstall.IndexOf(".exe") + 5) 79 | $uninstall = "$setup /s /f1`"$issFile`"" 80 | if ($PSBoundParameters.ContainsKey('LogDirectory')) { 81 | $issLog = "$LogDirectory\$($Name.Replace(' ', ''))-$(Get-Date -Format 'yyyyMMdd-HH-mm').log" 82 | $uninstall = $uninstall + " /f2`"$issLog`"" 83 | } 84 | 85 | $uninstallStringCount += 1 86 | Write-Host "`tUninstalling application with uninstall string '$uninstall'" 87 | if ($PSCmdlet.ShouldProcess("$Name", 'Uninstall')) { 88 | & cmd.exe /c $uninstall | Out-Host 89 | if ($LastExitCode -in $SuccessExitCodes) { 90 | Write-Host "`tExit Code: $LastExitCode" 91 | Write-Host "`tFinished uninstalling application with uninstall string '$uninstall'" 92 | } 93 | else { 94 | Write-Error "Exit code $LastExitCode uninstalling $Name" -ErrorAction 'Stop' 95 | return 96 | } 97 | } 98 | 99 | $newRegistryEntries = $null 100 | $newRegistryEntries = @(Get-RegistryEntry -Name $Name) 101 | if ($newRegistryEntries.Count -eq 0) { 102 | Write-Host "$Name no longer installed." 103 | return 104 | } 105 | } 106 | 107 | if ($uninstallStringCount -eq 0) { 108 | Write-Error "`tNo valid uninstall strings found" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Content/Module/Private/Remove-BloatwareWin32Msi.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareWin32Msi { 2 | [CmdletBinding( 3 | SupportsShouldProcess, 4 | PositionalBinding = $false 5 | )] 6 | param ( 7 | [Parameter(Mandatory)] 8 | [ValidateNotNullOrEmpty()] 9 | [string] 10 | $Name, 11 | 12 | [Parameter(Mandatory)] 13 | [ValidateNotNullOrEmpty()] 14 | $RegistryEntries, 15 | 16 | [Parameter()] 17 | [ValidateNotNullOrEmpty()] 18 | [string] 19 | $LogDirectory, 20 | 21 | [Parameter()] 22 | [switch] 23 | $WarnOnMissingInstallStringEveryTime, 24 | 25 | [Parameter()] 26 | [ValidateNotNullOrEmpty()] 27 | [string] 28 | $CustomSuffix, 29 | 30 | [Parameter()] 31 | [ValidateNotNullOrEmpty()] 32 | [int64[]] 33 | $SuccessExitCodes = @(0, 1707, 3010, 1641) 34 | ) 35 | 36 | $uninstallStringCount = 0 37 | foreach ($registryEntry in $RegistryEntries) { 38 | If ($registryEntry.UninstallString) { 39 | Write-Verbose "Remove-BloatwareWin32Msi: using UninstallString $($registryEntry.UninstallString)" 40 | $reGuid = '\{?(([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12})\}?' 41 | if ($registryEntry.UninstallString -match $reGuid) { 42 | 43 | $uninstall = "MsiExec.exe /qn /norestart /X{$($matches[1])}" 44 | if ($PSBoundParameters.ContainsKey('LogDirectory')) { 45 | $msiLog = "$LogDirectory\$($Name.Replace(' ', ''))-$(Get-Date -Format 'yyyyMMdd-HH-mm').log" 46 | $uninstall = $uninstall + " /L*V `"$msiLog`"" 47 | } 48 | if ($PSBoundParameters.ContainsKey('CustomSuffix')) { 49 | $uninstall = "$uninstall $CustomSuffix" 50 | } 51 | 52 | $uninstallStringCount += 1 53 | Write-Host "`tUninstalling application with uninstall string '$uninstall'" 54 | if ($PSCmdlet.ShouldProcess("$Name", 'Uninstall')) { 55 | & cmd.exe /c $uninstall | Out-Host 56 | 57 | if ($LastExitCode -in $SuccessExitCodes) { 58 | Write-Host "`tExit Code: $LastExitCode" 59 | Write-Host "`tFinished uninstalling application $Name" 60 | } 61 | else { 62 | Write-Error "Exit code $LastExitCode uninstalling $Name" -ErrorAction 'Stop' 63 | return 64 | } 65 | } 66 | } 67 | elseif ($WarnOnMissingInstallStringEveryTime) { 68 | Write-Warning "`tApplication uninstall string doesn't contain a standard GUID. Maybe there are two entries?" 69 | } 70 | } 71 | elseif ($warnOnMissingInstallStringEveryTime) { 72 | Write-Warning "`tApplication does not have an uninstall string. Maybe there are two entries?" 73 | } 74 | 75 | $newRegistryEntries = $null 76 | $newRegistryEntries = @(Get-RegistryEntry -Name $Name) 77 | if ($newRegistryEntries.Count -eq 0) { 78 | Write-Host "$Name no longer installed" 79 | return 80 | } 81 | } 82 | if ($uninstallStringCount -eq 0) { 83 | Write-Warning "`tNo valid uninstall strings found" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Content/Module/Private/Remove-BloatwareWin32Passthrough.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareWin32Passthrough { 2 | [CmdletBinding( 3 | SupportsShouldProcess, 4 | PositionalBinding = $false 5 | )] 6 | param ( 7 | [Parameter(Mandatory)] 8 | [ValidateNotNullOrEmpty()] 9 | [string] 10 | $Name, 11 | 12 | [Parameter(Mandatory)] 13 | [ValidateNotNullOrEmpty()] 14 | $RegistryEntries, 15 | 16 | [Parameter()] 17 | [ValidateNotNullOrEmpty()] 18 | [string] 19 | $LogDirectory, 20 | 21 | [Parameter()] 22 | [ValidateNotNullOrEmpty()] 23 | [int64[]] 24 | $SuccessExitCodes = @(0, 1707, 3010, 1641), 25 | 26 | [Parameter()] 27 | [ValidateNotNullOrEmpty()] 28 | [string] 29 | $CustomSuffix, 30 | 31 | [Parameter()] 32 | [switch] 33 | $MissingPathEqualsSuccess, 34 | 35 | [Parameter()] 36 | [switch] 37 | $MissingPathEqualsPrivateUninstallString, 38 | 39 | [Parameter()] 40 | [switch] 41 | $UsePrivateUninstallString 42 | 43 | ) 44 | 45 | $uninstallStringCount = 0 46 | 47 | :foreachRegistryEntry foreach ($registryEntry in $RegistryEntries) { 48 | $tryPrivateStringNext = $MissingPathEqualsPrivateUninstallString 49 | if ($UsePrivateUninstallString) { 50 | $uninstall = $registryEntry.PrivateUninstallString 51 | $tryPrivateStringNext = $false 52 | } else { 53 | $uninstall = $registryEntry.UninstallString 54 | } 55 | if(($null -eq $uninstall) -or ('' -eq $uninstall)) { 56 | if($tryPrivateStringNext -and ($null -ne $registryEntry.PrivateUninstallString) -and ('' -ne $registryEntry.PrivateUninstallString)) { 57 | $uninstall = $registryEntry.PrivateUninstallString 58 | $tryPrivateStringNext = $false 59 | } 60 | else { 61 | Write-Warning "`tRegistry entry UninstallString is null or empty" 62 | continue foreachRegistryEntry 63 | } 64 | } 65 | 66 | $uninstallStringCount += 1 67 | $uninstall = Format-UninstallString -UninstallString $uninstall 68 | if ($PSBoundParameters.ContainsKey('CustomSuffix')) { 69 | $uninstall = $uninstall + " $CustomSuffix" 70 | } 71 | Write-Host "`tUninstalling application with uninstall string '$uninstall'" 72 | if ($PSCmdlet.ShouldProcess("$Name", 'Uninstall')) { 73 | & cmd.exe /c $uninstall | Out-Host 74 | 75 | if ($LastExitCode -in $SuccessExitCodes) { 76 | Write-Host "`tExit Code: $LastExitCode" 77 | Write-Host "`tFinished uninstalling application $Name" 78 | } 79 | else { 80 | if (($LastExitCode -eq 1) -and $tryPrivateStringNext) { 81 | Write-Warning "Exit code $LastExitCode uninstalling $Name but MissingPathEqualsPrivateUninstallString is true" 82 | $params = @{ 83 | 'Name' = $Name 84 | 'RegistryEntries' = $registryEntry 85 | 'SuccessExitCodes' = $SuccessExitCodes 86 | 'CustomSuffix' = $CustomSuffix 87 | 'MissingPathEqualsSuccess' = $MissingPathEqualsSuccess 88 | 'UsePrivateUninstallString' = $UsePrivateUninstallString 89 | } 90 | #Logs not implemented 91 | Remove-BloatwareWin32Passthrough @params 92 | return 93 | } 94 | elseif (($LastExitCode -eq 1) -and ($MissingPathEqualsSuccess)) { 95 | Write-Warning "Exit code $LastExitCode uninstalling $Name but MissingPathEqualsSuccess is true" 96 | } 97 | else { 98 | Write-Error "Exit code $LastExitCode uninstalling $Name" -ErrorAction 'Stop' 99 | return 100 | } 101 | } 102 | } 103 | 104 | $newRegistryEntries = $null 105 | $newRegistryEntries = @(Get-RegistryEntry -Name $Name) 106 | if ($newRegistryEntries.Count -eq 0) { 107 | Write-Host "$Name no longer installed" 108 | return 109 | } 110 | } 111 | 112 | if ($uninstallStringCount -eq 0) { 113 | Write-Error "`tNo valid uninstall strings found" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Content/Module/Private/Test-VariableName.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Test-VariableName { 3 | <# 4 | .SYNOPSIS 5 | Tests if the variable name will need to be wrapped in ${} to be valid. 6 | 7 | .DESCRIPTION 8 | Tests if the variable name will need to be wrapped in ${} to be valid. 9 | Do not include the '$'. 10 | 11 | .PARAMETER Name 12 | The variable name to test. 13 | 14 | .EXAMPLE 15 | Test-VariableName -Name 'MyVariableName' 16 | 17 | .INPUTS 18 | Name, a string 19 | 20 | .OUTPUTS 21 | Boolean. True means the variable name is valid without using ${}. 22 | 23 | .NOTES 24 | Original Author: Sean Sauve 25 | #> 26 | [CmdletBinding()] 27 | [OutputType([bool])] 28 | param ( 29 | [Parameter(Mandatory, Position = 0)] 30 | [string] 31 | $Name 32 | ) 33 | 34 | $testingName = $Name 35 | if(($null -eq $testingName ) -or ('' -eq $testingName )) { 36 | $false 37 | return 38 | } 39 | 40 | if($Name.IndexOf("env:") -eq 0) { 41 | $testingName = $Name.Substring(4) 42 | } 43 | 44 | if('' -eq $testingName) { 45 | $false 46 | return 47 | } 48 | 49 | $validUnicodeCategories = @( 50 | 0 #Lu UppercaseLetter 51 | 1 #Ll LowercaseLetter 52 | 2 #Lt TitlecaseLetter 53 | 3 #Lm ModifierLetter 54 | 4 #Lo OtherLetter 55 | 8 #Nd DecimalDigitNumber 56 | ) 57 | 58 | $validOtherChars = @('_', '?') 59 | 60 | foreach($char in $testingName.ToCharArray()) { 61 | if(([Globalization.CharUnicodeInfo]::GetUnicodeCategory($char) -notin $validUnicodeCategories) -and ($char -notin $validOtherChars)) { 62 | $false 63 | return 64 | } 65 | } 66 | 67 | $true 68 | } 69 | -------------------------------------------------------------------------------- /Content/Module/Public/Remove-BloatwareAllAppxByPublisher.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareAllAppxByPublisher { 2 | #requires -RunAsAdministrator 3 | [OutputType([uint32])] 4 | [CmdletBinding( 5 | SupportsShouldProcess, 6 | PositionalBinding = $false 7 | )] 8 | param ( 9 | [Parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [string[]]$Publisher, 12 | 13 | [Parameter()] 14 | [ValidateNotNullOrEmpty()] 15 | [string[]]$BulkRemoveAllAppxExcludedApps 16 | ) 17 | 18 | [int]$errorCount = 00 19 | $packageTypeFilters = @( 20 | 'Bundle' 21 | 'Resource' 22 | 'Framework' 23 | 'Main' 24 | ) 25 | foreach($singlePublisher in $Publisher) { 26 | foreach ($packageTypeFilter in $packageTypeFilters) { 27 | $packages = Get-AppxPackage -AllUsers -PackageTypeFilter $packageTypeFilter | 28 | Where-Object {($_.Publisher -eq $singlePublisher) -or ($_.PublisherId -eq $singlePublisher)} 29 | if ($PSBoundParameters.ContainsKey('BulkRemoveAllAppxExcludedApps')) { 30 | $packages = $packages | Where-Object Name -NotIn $BulkRemoveAllAppxExcludedApps 31 | Write-Host "$($packages.Count) unexcluded Appx packages ($packageTypeFilter) found by publisher $singlePublisher" 32 | } 33 | else { 34 | Write-Host "$($packages.Count) Appx packages ($packageTypeFilter) found by publisher $singlePublisher" 35 | } 36 | $packageNames = $packages.Name | Sort-Object | Get-Unique -AsString 37 | foreach($packageName in $packageNames) { 38 | try { 39 | if ($PSCmdlet.ShouldProcess("$packageName", 'Remove')) { 40 | Remove-BloatwareAppx -PackageName $packageName -PackageTypeFilter $packageTypeFilter | Out-Null 41 | } 42 | } 43 | catch { 44 | Write-Warning "ERROR when removing application $packageName`:" 45 | Write-Warning $_.Exception.Message 46 | $errorCount += 1 47 | } 48 | } 49 | } 50 | } 51 | 52 | $errorCount 53 | } 54 | -------------------------------------------------------------------------------- /Content/Module/Public/Remove-BloatwareAllAppxProvisionedByPublisher.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareAllAppxProvisionedByPublisher { 2 | #requires -RunAsAdministrator 3 | [OutputType([uint32])] 4 | [CmdletBinding( 5 | SupportsShouldProcess, 6 | PositionalBinding = $false 7 | )] 8 | param ( 9 | [Parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [string[]]$PublisherId, 12 | 13 | [Parameter()] 14 | [ValidateNotNullOrEmpty()] 15 | [string[]]$BulkRemoveAllAppxExcludedApps 16 | ) 17 | 18 | [int]$errorCount = 0 19 | foreach($singlePublisherId in $PublisherId) { 20 | $packages = Get-AppxProvisionedPackage -Online -Verbose:$false | Where-Object PublisherId -eq $singlePublisherId 21 | if ($PSBoundParameters.ContainsKey('BulkRemoveAllAppxExcludedApps')) { 22 | $packages = $packages | Where-Object DisplayName -NotIn $BulkRemoveAllAppxExcludedApps 23 | Write-Host "$($packages.Count) unexcluded Appx Provisioned packages found by publisher $singlePublisherId" 24 | } 25 | else { 26 | Write-Host "$($packages.Count) Appx Provisioned packages found by publisher $singlePublisherId" 27 | } 28 | $packageNames = $packages.DisplayName | Sort-Object | Get-Unique -AsString 29 | foreach($packageName in $packageNames) { 30 | try { 31 | if ($PSCmdlet.ShouldProcess("$packageName", 'Remove')) { 32 | Remove-BloatwareAppxProvisioned -PackageName $packageName | Out-Null 33 | } 34 | } 35 | catch { 36 | Write-Warning "ERROR when removing application $packageName`:" 37 | Write-Warning $_.Exception.Message 38 | $errorCount += 1 39 | } 40 | } 41 | } 42 | 43 | $errorCount 44 | } 45 | -------------------------------------------------------------------------------- /Content/Module/Public/Remove-BloatwareAppx.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareAppx { 2 | #requires -RunAsAdministrator 3 | [CmdletBinding( 4 | SupportsShouldProcess, 5 | PositionalBinding = $false 6 | )] 7 | param ( 8 | [Parameter(Mandatory, ValueFromPipeline)] 9 | [ValidateNotNullOrEmpty()] 10 | [string[]]$PackageName, 11 | 12 | [string[]]$PackageTypeFilter = $null 13 | ) 14 | 15 | begin { 16 | if ($null -eq $PackageTypeFilter) { 17 | $packageTypeFilters = @( 18 | 'Bundle' 19 | 'Resource' 20 | 'Framework' 21 | 'Main' 22 | ) 23 | } 24 | else { 25 | $packageTypeFilters = $PackageTypeFilter 26 | } 27 | } 28 | 29 | process { 30 | foreach($singlePackageName in $PackageName) { 31 | foreach ($singlePackageTypeFilter in $packageTypeFilters) { 32 | $packages = Get-AppxPackage -AllUsers -PackageTypeFilter $singlePackageTypeFilter | 33 | Where-Object Name -match $singlePackageName 34 | foreach($package in $packages) { 35 | Write-Host "Removing Appx ($singlePackageTypeFilter) package $singlePackageName" 36 | if ($PSCmdlet.ShouldProcess("$singlePackageName", 'Remove')) { 37 | $package | Remove-AppxPackage -Allusers 38 | } 39 | Write-Host "`tFinished removing Appx ($singlePackageTypeFilter) package $singlePackageName" 40 | } 41 | } 42 | } 43 | } 44 | 45 | end { 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Content/Module/Public/Remove-BloatwareAppxProvisioned.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareAppxProvisioned { 2 | #requires -RunAsAdministrator 3 | [CmdletBinding( 4 | SupportsShouldProcess, 5 | PositionalBinding = $false 6 | )] 7 | param ( 8 | [Parameter(Mandatory, ValueFromPipeline)][string[]]$PackageName 9 | ) 10 | 11 | begin { 12 | $allAppxProvisioned = Get-AppxProvisionedPackage -Verbose:$false -Online 13 | } 14 | 15 | process { 16 | foreach($singlePackageName in $PackageName) { 17 | $packages = $allAppxProvisioned | Where-Object DisplayName -match $singlePackageName 18 | foreach($package in $packages) { 19 | Write-Host "Removing Appx Provisioned package $singlePackageName" 20 | if ($PSCmdlet.ShouldProcess("$singlePackageName", 'Remove')) { 21 | $package | Remove-AppxProvisionedPackage -AllUsers -Online -Verbose:$false 22 | } 23 | Write-Host "`tFinished removing Appx Provisioned package $singlePackageName" 24 | } 25 | } 26 | } 27 | 28 | end { 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Content/Module/Public/Remove-BloatwareWin32.ps1: -------------------------------------------------------------------------------- 1 | function Remove-BloatwareWin32 { 2 | #requires -RunAsAdministrator 3 | [CmdletBinding( 4 | DefaultParameterSetName = 'MSI', 5 | SupportsShouldProcess, 6 | PositionalBinding = $false 7 | )] 8 | param ( 9 | [Parameter(ParameterSetName = 'MSI', Mandatory)] 10 | [Parameter(ParameterSetName = 'Passthrough', Mandatory)] 11 | [Parameter(ParameterSetName = 'InstallShield', Mandatory)] 12 | [Parameter(ParameterSetName = 'Custom', Mandatory)] 13 | [ValidateNotNullOrEmpty()] 14 | [string] 15 | $Name, 16 | 17 | [Parameter(ParameterSetName = 'MSI')] 18 | [Parameter(ParameterSetName = 'Passthrough')] 19 | [Parameter(ParameterSetName = 'InstallShield')] 20 | [Parameter(ParameterSetName = 'Custom')] 21 | [ValidateNotNullOrEmpty()] 22 | [string] 23 | $LogDirectory, 24 | 25 | [Parameter(ParameterSetName = 'MSI')] 26 | [Parameter(ParameterSetName = 'Passthrough')] 27 | [Parameter(ParameterSetName = 'InstallShield')] 28 | [Parameter(ParameterSetName = 'Custom')] 29 | [ValidateNotNullOrEmpty()] 30 | [string[]] 31 | $DeleteRegistryKey, 32 | 33 | [Parameter(ParameterSetName = 'MSI')] 34 | [Parameter(ParameterSetName = 'Passthrough')] 35 | [Parameter(ParameterSetName = 'InstallShield')] 36 | [Parameter(ParameterSetName = 'Custom')] 37 | [switch] 38 | $DeleteUninstallKeyAfterRemove, 39 | 40 | [Parameter(ParameterSetName = 'MSI')] 41 | [Parameter(ParameterSetName = 'Passthrough')] 42 | [Parameter(ParameterSetName = 'InstallShield')] 43 | [Parameter(ParameterSetName = 'Custom')] 44 | [ValidateNotNullOrEmpty()] 45 | [int64[]] 46 | $SuccessExitCodes, 47 | 48 | [Parameter(ParameterSetName = 'Custom')] 49 | [Parameter(ParameterSetName = 'Passthrough')] 50 | [switch] 51 | $MissingPathEqualsSuccess, 52 | 53 | [Parameter(ParameterSetName = 'MSI')] 54 | [Parameter(ParameterSetName = 'Passthrough')] 55 | [Parameter(ParameterSetName = 'InstallShield')] 56 | [Parameter(ParameterSetName = 'Custom')] 57 | [switch] 58 | $RequiresDevelopment, 59 | 60 | [Parameter(ParameterSetName = 'MSI')] 61 | [switch] 62 | $FalseDoubleEntry, 63 | 64 | [Parameter(ParameterSetName = 'MSI')] 65 | [Parameter(ParameterSetName = 'Passthrough')] 66 | [Parameter(ParameterSetName = 'Custom')] 67 | [ValidateNotNullOrEmpty()] 68 | [string] 69 | $CustomSuffix, 70 | 71 | [Parameter(ParameterSetName = 'Passthrough')] 72 | [switch] 73 | $Passthrough, 74 | 75 | [Parameter(ParameterSetName = 'Passthrough')] 76 | [switch] 77 | $MissingPathEqualsPrivateUninstallString, 78 | 79 | [Parameter(ParameterSetName = 'Passthrough')] 80 | [switch] 81 | $UsePrivateUninstallString, 82 | 83 | [Parameter(ParameterSetName = 'InstallShield')] 84 | [switch] 85 | $InstallShield, 86 | 87 | [Parameter(ParameterSetName = 'InstallShield', Mandatory)] 88 | [ValidateNotNullOrEmpty()] 89 | [string] 90 | $ISSTemplate, 91 | 92 | [Parameter(ParameterSetName = 'InstallShield')] 93 | [switch] 94 | $ReplaceGUID, 95 | 96 | [Parameter(ParameterSetName = 'InstallShield')] 97 | [switch] 98 | $ReplaceVersion, 99 | 100 | [Parameter(ParameterSetName = 'Custom')] 101 | [switch] 102 | $Custom, 103 | 104 | [Parameter(ParameterSetName = 'Custom', Mandatory)] 105 | [ValidateNotNullOrEmpty()] 106 | [string[]] 107 | $CustomPaths, 108 | 109 | [Parameter(ParameterSetName = 'Custom')] 110 | [ValidateNotNullOrEmpty()] 111 | [string] 112 | $CustomArguments, 113 | 114 | [Parameter(ParameterSetName = 'Custom')] 115 | [switch] 116 | $MissingPathEqualsPassthrough, 117 | 118 | [Parameter(ParameterSetName = 'Custom')] 119 | [switch] 120 | $MissingPathEqualsMsi, 121 | 122 | [Parameter(ParameterSetName = 'Custom')] 123 | [switch] 124 | $ForceUninstallWithoutRegistry 125 | ) 126 | 127 | 128 | $registryEntries = @(Get-RegistryEntry -Name $Name) 129 | 130 | $forcingUninstall = (($registryEntries.Count -eq 0) -and $ForceUninstallWithoutRegistry) 131 | 132 | if (($registryEntries.Count -eq 0) -and (-not $forcingUninstall)) { 133 | Write-Host "$Name not installed." 134 | return 135 | } 136 | 137 | if ($forcingUninstall) { 138 | Write-Verbose "0 copies of $Name found but `$ForceUninstallWithoutRegistry is set." 139 | } 140 | else { 141 | Write-Host "Found $($registryEntries.Count) copies of $Name" 142 | } 143 | 144 | if ($RequiresDevelopment) { 145 | Write-Warning "`tApplication $Name uninstall is untested. Attempting to uninstall anyway." 146 | } 147 | 148 | if ($PSBoundParameters.ContainsKey('DeleteRegistryKey')) { 149 | foreach($key in $DeleteRegistryKey) { 150 | if (Test-Path $key) { 151 | Write-Host "`tDeleting key: $key" 152 | if ($PSCmdlet.ShouldProcess('Registry Key', 'Remove')) { 153 | Remove-Item -Path $key -Force -Recurse 154 | } 155 | } 156 | elseif (-not $forcingUninstall) { 157 | Write-Host "`tCould not find key specified in arguments: $key" 158 | } 159 | } 160 | } 161 | 162 | $params = @{'Name' = $Name} 163 | $params = Add-Hash -FromHashtable $PSBoundParameters -ToHashtable $params -Verbose:$false -KeyName @( 164 | 'LogDirectory' 165 | 'SuccessExitCodes' 166 | 'CustomSuffix' 167 | 'MissingPathEqualsSuccess' 168 | 'MissingPathEqualsPrivateUninstallString' 169 | 'UsePrivateUninstallString' 170 | 'ISSTemplate' 171 | 'ReplaceGUID' 172 | 'ReplaceVersion' 173 | 'CustomPaths' 174 | 'CustomArguments' 175 | 'MissingPathEqualsSuccess' 176 | 'MissingPathEqualsPassthrough' 177 | 'MissingPathEqualsMsi' 178 | ) 179 | 180 | if (-not $forcingUninstall) { 181 | $params += @{'RegistryEntries' = $registryEntries} 182 | } 183 | 184 | if ($PSCmdlet.ParameterSetName -eq 'MSI') { 185 | if ($FalseDoubleEntry) { 186 | Write-Host "`tApplication should use a standard uninstall string, but may have a false double entry in the registry." 187 | } 188 | else { 189 | Write-Host "`tApplication should use a standard uninstall string." 190 | $params += @{'WarnOnMissingInstallStringEveryTime' = $true} 191 | } 192 | 193 | Write-Verbose "Remove-BloatwareWin32: using these parameters to pass down:" 194 | foreach($key in $params.GetEnumerator()) { 195 | Write-Verbose "$($key.Name) : $($key.Value)" 196 | } 197 | 198 | Remove-BloatwareWin32Msi @params 199 | } 200 | elseif ($PSCmdlet.ParameterSetName -eq 'Passthrough') { 201 | Write-Host "`tApplication uses a custom command to uninstall stored in the UninstallString" 202 | 203 | Write-Verbose "Remove-BloatwareWin32: using these parameters to pass down:" 204 | foreach($key in $params.GetEnumerator()) { 205 | Write-Verbose "$($key.Name) : $($key.Value)" 206 | } 207 | 208 | #Logs not implemented 209 | Remove-BloatwareWin32Passthrough @params 210 | } 211 | elseif ($PSCmdlet.ParameterSetName -eq 'InstallShield') { 212 | Write-Host "`tApplication uses an InstallShield ISS file to uninstall." 213 | 214 | Write-Verbose "Remove-BloatwareWin32: using these parameters to pass down:" 215 | foreach($key in $params.GetEnumerator()) { 216 | Write-Verbose "$($key.Name) : $($key.Value)" 217 | } 218 | 219 | Remove-BloatwareWin32InstallShield @params 220 | } 221 | elseif ($PSCmdlet.ParameterSetName -eq 'Custom') { 222 | if (-not $forcingUninstall) { 223 | Write-Host "`tApplication uses a custom uninstaller." 224 | } 225 | $params['ForcingUninstall'] = $forcingUninstall 226 | 227 | Write-Verbose "Remove-BloatwareWin32: using these parameters to pass down:" 228 | foreach($key in $params.GetEnumerator()) { 229 | Write-Verbose "$($key.Name) : $($key.Value)" 230 | } 231 | 232 | #Logs not implemented 233 | Remove-BloatwareWin32Custom @params 234 | } 235 | 236 | if ($DeleteUninstallKeyAfterRemove) { 237 | foreach($regEntry in $registryEntries) { 238 | if (Test-Path $regEntry.PSPath) { 239 | Write-Host "`tDeleting registry key $($regEntry.PSPath)" 240 | if ($PSCmdlet.ShouldProcess('Registry Key', 'Remove')) { 241 | Remove-Item -Path $regEntry.PSPath -Force -Recurse 242 | } 243 | } 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /Content/Module/Public/Uninstall-Bloatware.ps1: -------------------------------------------------------------------------------- 1 | function Uninstall-Bloatware { 2 | #requires -RunAsAdministrator 3 | <# 4 | .SYNOPSIS 5 | Uninstalls undesirable applications that are pre-installed. 6 | 7 | .DESCRIPTION 8 | The Uninstall-Bloatware.ps1 script searches for applications in either 9 | Appx Provisioned, Appx, and Win32 format and removes them. 10 | 11 | This script expects the module and files for UninstallBloatware.psm1 12 | to be located in "$PSScriptRoot\Modules\" 13 | 14 | .PARAMETER LogDirectory 15 | Specifies the location to store the transcript and logs. 16 | 17 | If you do not specify this parameter, no transcript, tag file, or logs will be taken. 18 | 19 | .PARAMETER BulkRemoveAllAppxPublishers 20 | Specifies the publishers from which to remove Appx Provisioned and Appx 21 | packages. Use the the Publisher and PublisherId output from 22 | Get-AppxProvisionedPackage and Get-AppxPackage in order to determine the 23 | publishers. 24 | 25 | Optional. 26 | 27 | .PARAMETER BulkRemoveAllAppxExcludedApps 28 | Specifies apps to avoid removing with the Remove-BloatwareAllAppxByPublisher and 29 | Remove-BloatwareAllAppxProvisionedByPublisher functions 30 | 31 | .PARAMETER BloatwaresAppx 32 | Specifies the Appx Provisioned and Appx packages to remove. 33 | 34 | Optional. 35 | 36 | .PARAMETER BloatWaresWin32 37 | A string array specifying the Win32 applications to remove. If instructions 38 | have not yet been written for a particular Win32 application then this script 39 | will use MsiExec and warn ahead of time the application requires development. 40 | 41 | Some applications can't be removed before dependant applications have been 42 | removed. In this case, list the dependant applications first. 43 | 44 | Optional. 45 | 46 | .PARAMETER NoTranscript 47 | By default this script takes a transcript if you specify LogDirectory. 48 | Specify this switch to avoid taking a transcript. 49 | 50 | .PARAMETER NoTagFile 51 | By default this script creates the file UninstallBloatware.tag in LogDirectory 52 | when there were no errors. This is useful for InTune to know if this module has run successfully. 53 | Specify this switch to not create the tag file. 54 | 55 | .PARAMETER InstructionVariableNames 56 | Optionally specify the variables that will be searched for. Do not include the $ in variable names 57 | when specifying it in VariableNames. 58 | 59 | When reading JSON instruction files, Uninstall-Bloatware will search for each variable using 60 | up to three possible ways it could appear in the file: 61 | $VariableName 62 | $($VariableName) 63 | ${VariableName} 64 | 65 | Scope modifiers and namespaces other than Env: are not yet supported. 66 | 67 | Other than those in the Env: namespace, variable names that require ${} are not yet supported. 68 | 69 | The default value is: 70 | env:ProgramData 71 | env:ProgramFiles 72 | env:SystemDrive 73 | env:ProgramFiles(x86) 74 | env:CommonProgramW6432 75 | env:CommonProgramFiles(x86) 76 | env:DriverData 77 | env:CommonProgramFiles 78 | env:TEMP 79 | env:TMP 80 | env:ProgramW6432 81 | env:windir 82 | PSScriptRoot 83 | 84 | PSScriptRoot will be the directory of the JSON instruction file. 85 | 86 | When reading variables from within JSON instruction files any variable whose value contains 87 | a semicolon will throw an error. 88 | 89 | Does accepts $null or an empty string. 90 | 91 | .PARAMETER CustomWin32InstructionsDirectory 92 | Specify to add a directory to look in when searching for Win32 application 93 | uninstall instructions. Instructions in this directory will override the built-in 94 | application instructions. 95 | 96 | .EXAMPLE 97 | PS> Uninstall-Bloatware -LogDirectory "C:\Temp" -BloatwaresWin32 @('HP Sure Click', 'HP Sure Connect') 98 | 99 | .EXAMPLE 100 | PS> Uninstall-Bloatware -LogDirectory "C:\Temp" -BulkRemoveAllAppxPublishers @('v10z8vjag6ke6', 'CN=ED346674-0FA1-4272-85CE-3187C9C86E26') 101 | 102 | .EXAMPLE 103 | PS> Uninstall-Bloatware -LogDirectory "C:\Temp" -BloatwaresAppx @('HPAudioControl', 'HPDesktopSupportUtilities') 104 | 105 | .NOTES 106 | Original Author: Sean Sauve 107 | #> 108 | [CmdletBinding( 109 | SupportsShouldProcess, 110 | PositionalBinding = $false 111 | )] 112 | param ( 113 | [Parameter()] 114 | [ValidateNotNullOrEmpty()] 115 | [string]$LogDirectory, 116 | 117 | [Parameter()] 118 | [ValidateNotNullOrEmpty()] 119 | [string[]]$BulkRemoveAllAppxPublishers, 120 | 121 | [Parameter()] 122 | [ValidateNotNullOrEmpty()] 123 | [string[]]$BulkRemoveAllAppxExcludedApps, 124 | 125 | [Parameter()] 126 | [ValidateNotNullOrEmpty()] 127 | [string[]]$BloatwaresAppx, 128 | 129 | [Parameter()] 130 | [ValidateNotNullOrEmpty()] 131 | [string[]]$BloatwaresWin32, 132 | 133 | [Parameter()] 134 | [switch]$NoTranscript, 135 | 136 | [Parameter()] 137 | [switch]$NoTagFile, 138 | 139 | [Parameter()] 140 | [ValidateNotNullOrEmpty()] 141 | [string[]]$CustomWin32InstructionsDirectory, 142 | 143 | [Parameter()] 144 | [string[]] 145 | $InstructionVariableNames 146 | ) 147 | 148 | if ((-not $NoTranscript) -and $PSBoundParameters.ContainsKey('LogDirectory')) { 149 | Write-Host 'Starting transcript' 150 | if (-not (Test-Path $LogDirectory)) { 151 | New-Item -ItemType Directory -Path $LogDirectory -Force -WhatIf:$false -Confirm:$false | Out-Null 152 | } 153 | Start-Transcript "$LogDirectory\Transcript.log" -Append -WhatIf:$false -Confirm:$false 154 | } 155 | 156 | $errorCount = 0 157 | 158 | if ($PSBoundParameters.ContainsKey('BulkRemoveAllAppxPublishers')) { 159 | Write-Host 'Begin processing all Appx and Appx Provisioned packages by publisher' 160 | $BulkRemoveAllAppxExcludedAppsParam = @{} 161 | if ($PSBoundParameters.ContainsKey('BulkRemoveAllAppxExcludedApps')) { 162 | $BulkRemoveAllAppxExcludedAppsParam['BulkRemoveAllAppxExcludedApps'] = $BulkRemoveAllAppxExcludedApps 163 | } 164 | $errorCount += Remove-BloatwareAllAppxProvisionedByPublisher -PublisherId $BulkRemoveAllAppxPublishers @BulkRemoveAllAppxExcludedAppsParam 165 | $errorCount += Remove-BloatwareAllAppxByPublisher -Publisher $BulkRemoveAllAppxPublishers @BulkRemoveAllAppxExcludedAppsParam 166 | } 167 | else { 168 | Write-Host 'Skipping bulk removal of Appx and Appx Provisioned packages by publisher' 169 | } 170 | 171 | foreach($bloatwareAppx in $BloatwaresAppx) { 172 | Write-Host "Checking for Appx or Appx Provisioned package $bloatwareAppx." 173 | try { 174 | Remove-BloatwareAppxProvisioned -PackageName $bloatwareAppx 175 | Remove-BloatwareAppx -PackageName $bloatwareAppx 176 | } 177 | catch { 178 | Write-Warning "ERROR when removing application $bloatwareAppx`:" 179 | Write-Warning $_.Exception.Message 180 | $errorCount += 1 181 | } 182 | } 183 | 184 | if($PSBoundParameters.ContainsKey('LogDirectory')) { 185 | $logParam = @{'LogDirectory' = $LogDirectory} 186 | } 187 | else { 188 | $logParam = @{} 189 | } 190 | $getInstructionsParams = @{} 191 | if($PSBoundParameters.ContainsKey('CustomWin32InstructionsDirectory')) { 192 | $getInstructionsParams['CustomDirectory'] = $CustomWin32InstructionsDirectory 193 | } 194 | if($PSBoundParameters.ContainsKey('InstructionVariableNames')) { 195 | $getInstructionsParams['InstructionVariableNames'] = $InstructionVariableNames 196 | } 197 | if ($PSBoundParameters.ContainsKey('BloatwaresWin32')) { 198 | Write-Host 'Begin processing specific Win32 apps' 199 | foreach($bloatware in $BloatwaresWin32) { 200 | try { 201 | $instructions = Get-BloatwareWin32Instructions -Name $bloatware @getInstructionsParams 202 | Remove-BloatwareWin32 @instructions @logParam 203 | } 204 | catch { 205 | Write-Warning "ERROR when removing application $bloatware`:" 206 | Write-Warning $_.Exception.Message 207 | $errorCount += 1 208 | } 209 | } 210 | } 211 | else { 212 | Write-Host 'Skipping removal of Win32 apps' 213 | } 214 | 215 | if ($errorCount -eq 0) { 216 | if($NoTagFile -eq $true) { 217 | Write-Host 'NoTagFile parameter is true, not creating a tag file' 218 | } 219 | elseif(-not ($PSBoundParameters.ContainsKey('LogDirectory'))) { 220 | Write-Host 'LogDirectory not provided, not creating a tag file' 221 | } 222 | else { 223 | Write-Host 'Creating a tag file so that Intune knows this was ran successfully' 224 | Set-Content -Path "$LogDirectory\UninstallBloatware.tag" -Value 'Success' -WhatIf:$false -Confirm:$false 225 | } 226 | } 227 | else { 228 | Write-Warning "$errorCount errors encountered" 229 | } 230 | 231 | if ((-not $NoTranscript) -and $PSBoundParameters.ContainsKey('LogDirectory')) { 232 | Write-Host 'Stopping Transcript' 233 | Stop-Transcript 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /Content/Module/UninstallBloatware.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'UninstallBloatware.psm1' 3 | # 4 | # Generated by: Sean Sauve 5 | # 6 | # Generated on: 16 July 2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'UninstallBloatware.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.1.7' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'dbb629b9-c6aa-46e0-b0aa-cb4cb57e889f' 22 | 23 | # Author of this module 24 | Author = 'Sean Sauve' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'The Salvation Army' 28 | 29 | # Copyright statement for this module 30 | Copyright = 'MIT License, Copyright (c) 2021 The Salvation Army, USA Southern Territory, https://github.com/TSA-SAUSS/UninstallBloatware/blob/main/LICENSE' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Uninstalls undesirable applications that are pre-installed ' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = @( 73 | 'Remove-BloatwareAllAppxByPublisher' 74 | 'Remove-BloatwareAllAppxProvisionedByPublisher' 75 | 'Remove-BloatwareWin32' 76 | 'Remove-BloatwareAppx' 77 | 'Remove-BloatwareAppxProvisioned' 78 | 'Uninstall-Bloatware' 79 | ) 80 | 81 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 82 | CmdletsToExport = @() 83 | 84 | # Variables to export from this module 85 | VariablesToExport = @() 86 | 87 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 88 | AliasesToExport = @() 89 | 90 | # DSC resources to export from this module 91 | # DscResourcesToExport = @() 92 | 93 | # List of all modules packaged with this module 94 | # ModuleList = @() 95 | 96 | # List of all files packaged with this module 97 | # FileList = @() 98 | 99 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 100 | PrivateData = @{ 101 | 102 | PSData = @{ 103 | 104 | # Tags applied to this module. These help with module discovery in online galleries. 105 | # Tags = @() 106 | 107 | # A URL to the license for this module. 108 | # LicenseUri = '' 109 | 110 | # A URL to the main website for this project. 111 | # ProjectUri = '' 112 | 113 | # A URL to an icon representing this module. 114 | # IconUri = '' 115 | 116 | # ReleaseNotes of this module 117 | # ReleaseNotes = '' 118 | 119 | } # End of PSData hashtable 120 | 121 | } # End of PrivateData hashtable 122 | 123 | # HelpInfo URI of this module 124 | # HelpInfoURI = '' 125 | 126 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 127 | # DefaultCommandPrefix = '' 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Content/Module/UninstallBloatware.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | =========================================================================== 4 | Created on: 2021/07/16 5 | Modified on: 2021/11/30 6 | Created by: Sean Sauve 7 | Organization: TSA-SAUSS 8 | Version: 1.1.7 9 | =========================================================================== 10 | .DESCRIPTION 11 | Uninstalls undesirable applications that are pre-installed 12 | #> 13 | 14 | $PrivatePS1Files = Get-ChildItem -Name "$PSScriptRoot\Private\*.ps1" 15 | foreach ($PS1File in $PrivatePS1Files) { 16 | . $PSScriptRoot\Private\$($PS1File.split(".")[0]) 17 | } 18 | 19 | $PublicPS1Files = Get-ChildItem -Name "$PSScriptRoot\Public\*.ps1" 20 | foreach ($PS1File in $PublicPS1Files) { 21 | . $PSScriptRoot\Public\$($PS1File.split(".")[0]) 22 | } 23 | 24 | $ExportFunctions = @( 25 | 'Remove-BloatwareAllAppxByPublisher' 26 | 'Remove-BloatwareAllAppxProvisionedByPublisher' 27 | 'Remove-BloatwareWin32' 28 | 'Remove-BloatwareAppx' 29 | 'Remove-BloatwareAppxProvisioned' 30 | 'Uninstall-Bloatware' 31 | ) 32 | 33 | Export-ModuleMember -Function $ExportFunctions -Alias * -Cmdlet * 34 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Client Security Manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Client Security Manager", 3 | "FalseDoubleEntry": true 4 | } 5 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Collaboration Keyboard For Cisco UCC.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Collaboration Keyboard For Cisco UCC" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Collaboration Keyboard for Skype for Business.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Collaboration Keyboard for Skype for Business" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Connection Optimizer.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Connection Optimizer", 3 | "InstallShield": true, 4 | "ReplaceGUID": true, 5 | "ReplaceVersion": true, 6 | "ISSTemplate": "$PSScriptRoot\\ISS-HPConnectionOptimizer.iss" 7 | } 8 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Device Access Manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Device Access Manager" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Documentation.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Documentation", 3 | "Passthrough": true 4 | } 5 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP JumpStart Apps.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP JumpStart Apps", 3 | "Passthrough": true 4 | } 5 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP JumpStart Bridge.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP JumpStart Bridge" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP JumpStart Launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP JumpStart Launch" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Notifications.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Notifications" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Recovery Manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Recovery Manager" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Security Update Service.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Security Update Service" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP SoftPaq Download Manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP SoftPaq Download Manager" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Software Setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Software Setup" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Support Assistant.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Support Assistant", 3 | "Custom": true, 4 | "CustomPaths": [ 5 | "${env:ProgramFiles(x86)}\\Hewlett-Packard\\HP Support Framework\\UninstallHPSA.exe", 6 | "${env:ProgramFiles(x86)}\\HP\\HP Support Framework\\UninstallHPSA.exe" 7 | ], 8 | "CustomArguments": "/s /v/qn UninstallKeepPreferences=FALSE", 9 | "DeleteRegistryKey": "HKLM:\\Software\\WOW6432Node\\Hewlett-Packard\\HPActiveSupport", 10 | "SuccessExitCodes": [0, 1707, 3010, 1641, 1605], 11 | "DeleteUninstallKeyAfterRemove": true, 12 | "MissingPathEqualsMsi": true 13 | } 14 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Support Solutions Framework.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Support Solutions Framework" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Sure Click.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Sure Click" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Sure Connect.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Sure Connect", 3 | "InstallShield": true, 4 | "ReplaceGUID": true, 5 | "ReplaceVersion": true, 6 | "ISSTemplate": "$PSScriptRoot\\ISS-HPConnectionOptimizer.iss" 7 | } 8 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Sure Recover.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Sure Recover" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Sure Run.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Sure Run" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Sure Sense Installer.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Sure Sense Installer" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP System Software Manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP System Software Manager" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Velocity.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Velocity", 3 | "Passthrough": true, 4 | "MissingPathEqualsPrivateUninstallString": true, 5 | "CustomSuffix": "/S" 6 | } 7 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP Wolf Security.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP Wolf Security" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/HP WorkWise.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "HP WorkWise", 3 | "Passthrough": true, 4 | "CustomSuffix": "-silent" 5 | } 6 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/IPMPLUS.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "IPMPLUS" 3 | } 4 | -------------------------------------------------------------------------------- /Content/Module/Win32Instructions/ISS-HPConnectionOptimizer.iss: -------------------------------------------------------------------------------- 1 | [InstallShield Silent] 2 | Version=v7.00 3 | File=Response File 4 | [File Transfer] 5 | OverwrittenReadOnly=NoToAll 6 | [$GUID-DlgOrder] 7 | Dlg0=$GUID-SdWelcomeMaint-0 8 | Count=3 9 | Dlg1=$GUID-MessageBox-0 10 | Dlg2=$GUID-SdFinishReboot-0 11 | [$GUID-SdWelcomeMaint-0] 12 | Result=303 13 | [$GUID-MessageBox-0] 14 | Result=6 15 | [Application] 16 | Name=HP Connection Optimizer 17 | Version=$VERSION 18 | Company=HP Inc. 19 | Lang=0409 20 | [$GUID-SdFinishReboot-0] 21 | Result=1 22 | BootOption=0 -------------------------------------------------------------------------------- /Content/UninstallBloatwareSample.ps1: -------------------------------------------------------------------------------- 1 | Import-Module "$PSScriptRoot\Module\UninstallBloatware.psm1" -Force 2 | 3 | $logDirectory = "$env:ProgramData\UninstallBloatware" 4 | 5 | $bulkRemoveAllAppxPublishers = @( 6 | 'v10z8vjag6ke6' 7 | 'CN=ED346674-0FA1-4272-85CE-3187C9C86E26' 8 | ) 9 | 10 | $bloatwaresAppx = @( 11 | 'HPAudioControl' 12 | 'myHP' 13 | 'HPJumpStart' 14 | 'HPSupportAssistant' 15 | 'HPPrivacySettings' 16 | 'HPPCHardwareDiagnosticsWindows' 17 | 'HPDesktopSupportUtilities' 18 | ) 19 | 20 | $bloatwaresWin32 = @( 21 | 'HP Collaboration Keyboard For Cisco UCC' 22 | 'HP Collaboration Keyboard for Skype for Business' 23 | 'HP Connection Optimizer' 24 | 'HP Device Access Manager' 25 | 'HP Documentation' 26 | 'HP JumpStart Bridge' 27 | 'HP JumpStart Launch' 28 | 'HP JumpStart Apps' 29 | 'HP Notifications' 30 | 'HP Recovery Manager' 31 | 'HP Security Update Service' 32 | 'HP SoftPaq Download Manager' 33 | 'HP Software Setup' 34 | 'HP Support Assistant' 35 | 'HP Sure Click' 36 | 'HP Sure Connect' 37 | 'HP Sure Recover' 38 | 'HP Sure Run' 39 | 'HP Sure Sense Installer' 40 | 'HP System Software Manager' 41 | 'HP Velocity' 42 | 'HP Wolf Security' 43 | 'HP WorkWise' 44 | 'IPMPLUS' 45 | 'HP Support Solutions Framework' 46 | 'HP Client Security Manager' 47 | ) 48 | 49 | $params = @{ 50 | 'LogDirectory' = $logDirectory 51 | 'BulkRemoveAllAppxPublishers' = $bulkRemoveAllAppxPublishers 52 | 'BloatwaresAppx' = $bloatwaresAppx 53 | 'BloatwaresWin32' = $bloatwaresWin32 54 | } 55 | Uninstall-Bloatware @params 56 | -------------------------------------------------------------------------------- /InTune.md: -------------------------------------------------------------------------------- 1 | # Uninstall Bloatware 2 | 3 | ## Description 4 | 5 | Uninstalls "bloatware" that comes preinstalled on computers. 6 | 7 | ## Instructions 8 | 9 | To use this module in InTune: 10 | 11 | 1. Install .NET Core SDK and IntuneAppBuilder using the instructions here https://github.com/simeoncloud/IntuneAppBuilder. 12 | 2. Customize UninstallBloatwareSample.ps1 using the instructions here https://github.com/TSA-SAUSS/UninstallBloatware/blob/main/README.md. 13 | 3. Run New-InTuneWinPackage.ps1 to create the Content.portal.intunewin file. 14 | 4. Create the Win32 app in InTune using the settings below. If you've changed the filename of UninstallBloatwareSample.ps1 or the log folder, adjust your Win32 app settings accordingly. 15 | 16 | ## Custom Settings 17 | 18 | Runs the script Uninstall-Bloatware.ps1 19 | 20 | The installation script stores logs in C:\ProgramData\UninstallBloatware 21 | 22 | The file C:\ProgramData\UninstallBloatware\UninstallBloatware.tag will be added to the computer after a successfull run. 23 | 24 | ## App Information 25 | 26 | | Property | Value | 27 | | --------------- | --------------------------------------------------------------------------------------------------------------------------- | 28 | | INTUNEWIN file | ./Output/Content.portal.intunewin | 29 | | Name | Uninstall Bloatware | 30 | | Description | Uninstall Bloatware | 31 | | Publisher | The Salvation Army | 32 | | App Version | 1.1.7 | 33 | | Category | Computer Management | 34 | | Information URL | https://github.com/TSA-SAUSS/UninstallBloatware/blob/main/README.md | 35 | 36 | ## Program 37 | 38 | | Property | Value | 39 | | ----------------------- | --------------------------------------------------------------------------------------- | 40 | | Install Command | %SYSTEMROOT%\Sysnative\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\UninstallBloatwareSample.ps1 | 41 | | Uninstall Command | cmd.exe /c del %PROGRAMDATA%\UninstallBloatware\UninstallBloatware.tag | 42 | | Install Behavior | System | 43 | | Device restart behavior | No specific action | 44 | | Return Codes | 0: Success
1707: Success
3010: Soft Reboot
1641: Hard Reboot
1618: Retry
1605: Unknown Product | 45 | 46 | ## Requirements 47 | 48 | | Property | Value | 49 | | ----------------------------- | --------------- | 50 | | Operating system architecture | 64-bit | 51 | | Minimum operating system | Windows 10 1607 | 52 | 53 | ## Detection Rules 54 | 55 | Rules Format: Manually configure detection rules 56 | 57 | ### Rule 1 58 | 59 | | Property | Value | 60 | | ---------------------------------------------- | ------------------------------------------------------- | 61 | | Type | File | 62 | | Path | %PROGRAMDATA%\UninstallBloatware | 63 | | File or folder | UninstallBloatware.tag | 64 | | Detection method | File or folder exists | 65 | | Associated with a 32-bit app on 64-bit clients | No | 66 | 67 | ## Dependencies 68 | 69 | None 70 | 71 | ## Supersedence 72 | 73 | None 74 | 75 | ## Assignments 76 | 77 | Recommend this be installed for all Autopilot computers. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 The Salvation Army, USA Southern Territory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /New-InTuneWinPackage.ps1: -------------------------------------------------------------------------------- 1 | Function New-InTuneWinPackage { 2 | [CmdletBinding()] 3 | param ( 4 | [string]$AppSubfolderName 5 | ) 6 | 7 | try { 8 | $dotnetVersion = & dotnet --version 9 | } catch { 10 | Write-Error "Cannot find .NET Core SDK. Please install version 3.1 or higher" 11 | } 12 | if ($dotnetVersion -lt 3.1.408) { 13 | Write-Error "Requires .NET Core SDK version 3.1 or higher." 14 | } 15 | if (-not ((dotnet tool list --global) -like "*intuneappbuilder.console*")) { 16 | Write-Error "Intuneappbuilder is not installed. Please see instructions to install it here: https://github.com/simeoncloud/IntuneAppBuilder" 17 | } 18 | 19 | Write-Host "------Making intunewin file------" 20 | if ($PSBoundParameters.ContainsKey('AppSubfolderName')) { 21 | $appFolder = "$PSScriptRoot\$AppSubfolderName" 22 | } 23 | else { 24 | $appFolder = "$PSScriptRoot" 25 | } 26 | IntuneAppBuilder pack --source $appFolder\Content --output $appFolder\Output 27 | 28 | Write-Host "" 29 | Write-Host "Review the JSON file 'Content.intunewin.json'." 30 | Write-Host "When ready upload 'Content.portal.intunewin' to the InTune portal" 31 | } 32 | 33 | New-InTuneWinPackage 34 | -------------------------------------------------------------------------------- /Output/Content.intunewin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TSA-SAUSS/UninstallBloatware/9bdfcae230faecfe6eedecc7c979451f04820bd8/Output/Content.intunewin -------------------------------------------------------------------------------- /Output/Content.intunewin.json: -------------------------------------------------------------------------------- 1 | { 2 | "App": { 3 | "installExperience": { 4 | "runAsAccount": "system", 5 | "@odata.type": "microsoft.graph.win32LobAppInstallExperience" 6 | }, 7 | "setupFilePath": "Content.intunewin.zip", 8 | "fileName": "Content.intunewin", 9 | "displayName": "Content", 10 | "@odata.type": "microsoft.graph.win32LobApp" 11 | }, 12 | "File": { 13 | "name": "Content.intunewin", 14 | "size": 28105, 15 | "sizeEncrypted": 28160, 16 | "@odata.type": "microsoft.graph.mobileAppContentFile" 17 | }, 18 | "EncryptionInfo": { 19 | "encryptionKey": "LJrVVzJieJLTf0p+u/60plkzAWdmmyv2uB4TdJBwl0I=", 20 | "initializationVector": "9gRTq+bYRicEpEqt/47jmQ==", 21 | "mac": "xjihcCmpoRqZzxzGsZInDWZA7mBzQLywdJ1q2asNZW0=", 22 | "macKey": "T1O5TlCMtzH+XjKDJaVGV/3tzzFdektwLa0flGhQtc8=", 23 | "profileIdentifier": "ProfileVersion1", 24 | "fileDigest": "EGMnmr+mocevlmX8CHzmgRBl0PiQoo3ZRtoFarCqr+k=", 25 | "fileDigestAlgorithm": "SHA256", 26 | "@odata.type": "microsoft.graph.fileEncryptionInfo" 27 | } 28 | } -------------------------------------------------------------------------------- /Output/Content.portal.intunewin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TSA-SAUSS/UninstallBloatware/9bdfcae230faecfe6eedecc7c979451f04820bd8/Output/Content.portal.intunewin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UninstallBloatware 2 | 3 | Using this module, you can easily specify a list of applications to remove, whether they are traditional Win32 applications or modern AppX packages. Since not every Win32 application can be removed without a little extra work, the module was designed to allow instructions for specific applications to be pulled from a JSON file. 4 | 5 | ## Why is this needed? 6 | 7 | This project was created to remove pre-installed applications by computer manufacturers, typically referred to as "bloatware." As recently as two or three years ago, the typical best practices within most companies were to freshly reimage new computers with a custom image, thereby removing the applications that weren't needed or wanted, and adding applications and settings that were needed through a "baked-in" image. However, with the advent of Windows Autopilot, many organizations are direct shipping computers to the end-user, and customizing the operating system's programs and settings programmatically. 8 | 9 | The only way to remove some bloatware when direct shipping computers to end-users is to remove the applications manually or deploy custom scripts. That is the goal of this project: to make an extensible tool that can be used by InTune to uninstall applications. 10 | 11 | ### When is this not needed? 12 | * Windows in-box apps. Use InTune to remove them. 13 | * Applications that remove with simple MsiExec commands. There are many good ways to do this, and winget will hopefully make it even easier when winget is fully released. 14 | 15 | ## License 16 | 17 | This module is being released under an MIT license. Please consider giving back with any code improvements or complex instruction files that might be valuable to other organizations. 18 | 19 | ## Usage - ExecutionPolicy 20 | 21 | Your ExecutionPolicy will need to be RemoteSigned or Bypass. 22 | ```powershell 23 | Set-ExecutionPolicy -ExecutionPolicy 'RemoteSigned' 24 | ``` 25 | 26 | In most cases, if your ExecutionPolicy is RemoteSigned you will need to make sure that you unblock these files. Download the source as a zip file and unblock the entire zip file before extracting it. 27 | 28 | ```powershell 29 | Unblock-File .\UninstallBloatware-main.zip 30 | ``` 31 | 32 | ## Usage - Sample 33 | 34 | To get started using this module, check out UninstallBloatwareSample.ps1 and customize it to meet your needs. 35 | 36 | ## Usage - InTune 37 | 38 | To use this module with InTune, see the instructions here: https://github.com/TSA-SAUSS/UninstallBloatware/blob/main/InTune.md 39 | 40 | ## Parameters 41 | 42 | ### BloatwaresAppx - Uninstall Appx Packages by Name 43 | 44 | First of all, InTune can uninstall in-box Windows applications that will allow those applications to be reprovisioned at a later date without reimaging the device if you change your mind. For that reason, this module is not recommended for uninstalling in-box apps for organizations with InTune. 45 | 46 | UninstallBloatware will run a regex match against package names. UninstallBloatware will uninstall both AppX and AppX provisioned packages; it makes no difference. 47 | 48 | To run UninstallBloatware to uninstall AppX packages, you can specify the package name. 49 | ```powershell 50 | Import-Module .\Module\UninstallBloatware.psm1 51 | Uninstall-Bloatware -BloatwaresAppx @('HPAudioControl') 52 | ``` 53 | ### BulkRemoveAllAppxPublishers - Uninstall Appx Packages by Publisher 54 | To remove all AppX by a certain publisher, you can specify their publisher ID. UninstallBloatware will run a regex match for that publisher. 55 | 56 | To uninstall all AppX according to the publisher Id: 57 | ```powershell 58 | Import-Module .\Module\UninstallBloatware.psm1 59 | Uninstall-Bloatware -BulkRemoveAllAppxPublishers @('v10z8vjag6ke6', 'CN=ED346674-0FA1-4272-85CE-3187C9C86E26') 60 | ``` 61 | 62 | 63 | ### BloatwaresWin32 - Uninstall Win32 Applications 64 | 65 | To run UninstallBloatware to uninstall Win32 applications: 66 | ```powershell 67 | Import-Module .\Module\UninstallBloatware.psm1 68 | Uninstall-Bloatware -LogDirectory "C:\Temp" -BloatwaresWin32 @('HP Sure Click', 'HP Sure Connect') 69 | ``` 70 | 71 | If the application uninstalls with typical MSI parameters, it does not need to have a JSON file. If that works for all of your applications, then you may want to check out 'winget uninstall' once it's released. 72 | 73 | If the application uninstall requires specific steps or instructions beyond msiexec, those instructions will need to be stored in a directory specified by the parameter CustomWin32InstructionsDirectory, or in ./Module/Win32Instructions/. 74 | 75 | ### LogDirectory - Where to Store Logs 76 | 77 | Specifies the location to store the transcript and logs. 78 | If you do not specify this parameter, no transcript, tag file, or logs will be taken. 79 | 80 | ### CustomWin32InstructionsDirectory - Where custom instructions are stored 81 | 82 | Specify to add a directory to look in when searching for Win32 application uninstall instructions. Instructions in this directory will override the built-in application instructions. 83 | ```powershell 84 | Uninstall-Bloatware -LogDirectory "C:\Temp" -BloatwaresWin32 @('HP Sure Click', 'HP Sure Connect') -CustomWin32InstructionsDirectory @('C:\Temp') 85 | ``` 86 | 87 | ### NoTranscript - Don't take a transcript 88 | 89 | By default this script takes a transcript if you specify LogDirectory. Specify this switch to avoid taking a transcript. 90 | 91 | ### NoTagFile - Don't create a tag file 92 | 93 | By default this script creates the file UninstallBloatware.tag in LogDirectory when there were no errors. This is useful for InTune to know if this module has run successfully. Specify this switch to not create the tag file. 94 | 95 | ### InstructionVariableNames - Custom set of variable names that can be used in the instruction files 96 | 97 | Optionally specify the variables that will be searched for. Do not include the $ in variable names when specifying them here. 98 | 99 | When reading JSON instruction files, Uninstall-Bloatware will search for each variable using up to three possible ways it could appear in the file:
100 | * $VariableName
101 | * $($VariableName)
102 | * ${VariableName}
103 | 104 | Scope modifiers and namespaces other than Env: are not yet supported. 105 | 106 | Other than those in the Env: namespace, variable names that require ${} are not yet supported. 107 | 108 | The default value is: 109 | ```powershell 110 | @( 111 | 'env:ProgramData' 112 | 'env:ProgramFiles' 113 | 'env:SystemDrive' 114 | 'env:ProgramFiles(x86)' 115 | 'env:CommonProgramW6432' 116 | 'env:CommonProgramFiles(x86)' 117 | 'env:DriverData' 118 | 'env:CommonProgramFiles' 119 | 'env:TEMP' 120 | 'env:TMP' 121 | 'env:ProgramW6432' 122 | 'env:windir' 123 | 'PSScriptRoot' 124 | ) 125 | ``` 126 | 127 | PSScriptRoot will be the directory of the JSON instruction file. 128 | 129 | When reading variables from within JSON instruction files, any variable whose value contains a semicolon will throw an error. 130 | 131 | Does accepts $null or an empty string, which will effectively disable the ability to store variables in the instructions files. 132 | 133 | ## Customizing - Instruction Files 134 | 135 | Instruction files help specify custom parameters or steps that need to be taken to remove the more pesky Win32 applications. Name the file the same as the application but with the .json extension. Store it in either ./Module/Win32Instructions/ or a directory specified in CustomWin32InstructionsDirectory. 136 | 137 | Some parameters can be specified for any Win32 application, while some parameters are only available to some types of uninstallers (Passthrough, Installshield, or Custom). 138 | 139 | This module supports using environment variables, or really any variable with some exceptions listed above, in your instructions files. The format of these variable names need to be either ${env:variablename}, $($env:variablename), or $env:variablename. Note that ${env:ProgramFiles(x86)}, and any other variable name that would normally require braces, can only be specified using the braces notation. Use $PSScriptRoot in the instructions to refer to the location of the JSON instructions file itself. 140 | 141 | To specify a custom list of variables allowed in the instructions files see the parameter [InstructionVariableNames](https://github.com/TSA-SAUSS/UninstallBloatware#instructionvariablenames---custom-set-of-variable-names-that-can-be-used-in-the-instruction-files) 142 | 143 | ### Common Parameters 144 | 145 | | Parameter | Type | Description 146 | | --- | --- | --- | 147 | | Name | string | The name of the application. | 148 | | DeleteRegistryKey | string array | Registry keys to delete before uninstalling the application. | 149 | | DeleteUninstallKeyAfterRemove | boolean | Specify to delete the uninstall registry entries for this application after a successful uninstall.
Some applications don't always do this. | 150 | | SuccessExitCodes | integer array | Specify if the application returns non-standard exit codes even when the uninstall is a success. The default value is @(0, 1707, 3010, 1641) | 151 | | RequiresDevelopment | boolean | Specify if the application's uninstall is not fully tested. If no instructions files are found, the application will have this set by default | 152 | 153 | 154 | ### MSI Parameters 155 | 156 | MSI is used if the application can be silently uninstalled with the ProductGuid stored in the registry and MsiExec. MSI is the default way Win32 applications are uninstalled if no parameter is used and works for most well-formed applications. MSI is the preferred method, and you should try it first. 157 | 158 | Parameters: 159 | 160 | | Parameter | Type | Description 161 | | --- | --- | --- | 162 | | FalseDoubleEntry | boolean | Some applications have two registry entries. Specifying this avoids warnings if one of them doesn't work well | 163 | | CustomSuffix | string | Arguments to be added to the end of the MsiExec uninstall command. | 164 | 165 | ### Passthrough 166 | 167 | Use this method if the UninstallString value stored in the application's registry key doesn't contain the ProductGuid but contains the path of the EXE and arguments to uninstall the application. 168 | 169 | | Parameter | Type | Description 170 | | --- | --- | --- | 171 | | Passthrough | boolean | Specify to ensure that the module knows unambiguously to use the Passthrough methodology. | 172 | | CustomSuffix | string | Arguments to be added to the end of the UninstallString value. | 173 | | MissingPathEqualsSuccess | boolean | If set to true and running the UninstallString returns 1 (file not found), that will be considered as
a success | 174 | | MissingPathEqualsPrivateUninstallString | boolean | If set to true and running the UninstallString returns 1 (file not found), use the
PrivateUninstallString instead | 175 | | UsePrivateUninstallString | boolean | Always use the PrivateUninstallString to remove the application. | 176 | 177 | ### InstallShield 178 | 179 | InstallShield applications can often be slightly tricky to uninstall silently. This method requires an ISS answer file. UninstallBloatware will read an ISS answer template file and use it to uninstall the application silently. The template file can contain the text $GUID and $Version, which will be replaced at run time with the proper ProductGUID and DisplayVersion, provided the parameters to do this are true. 180 | 181 | | Parameter | Type | Description 182 | | --- | --- | --- | 183 | | InstallShield | boolean | Specify to ensure that the module knows unambiguously to use the InstallShield methodology. | 184 | | ISSTemplate | string | Path to the ISS template file. By using $PSScriptRoot, you can specify the path relative to the JSON instructions file. | 185 | | ReplaceGUID | boolean | After reading the ISS template file, replace all occurrences of $GUID with the application's ProductGUID. | 186 | | ReplaceVersion | boolean | After reading the ISS template file, replaces all occurrences of $Version with the application's version. | 187 | --------------------------------------------------------------------------------