├── .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 |
--------------------------------------------------------------------------------