source/Scripts/Configuration/Install-AVDClient.ps1 (634 lines of code) (raw):

Param ( [Parameter(Mandatory = $false)] [string]$DeploymentType = "Install" ) #region Initialization $SoftwareName = 'Remote Desktop' $downloadUrl = "https://go.microsoft.com/fwlink/?linkid=2068602" $MSIArguments= "/qn ALLUSERS=1" $Script:FullName = $MyInvocation.MyCommand.Path $Script:File = $MyInvocation.MyCommand.Name $Script:Name=[System.IO.Path]::GetFileNameWithoutExtension($Script:File) $Script:Args = $null If ($ENV:PROCESSOR_ARCHITEW6432 -eq "AMD64") { Try { foreach($k in $MyInvocation.BoundParameters.keys) { switch($MyInvocation.BoundParameters[$k].GetType().Name) { "SwitchParameter" {if($MyInvocation.BoundParameters[$k].IsPresent) { $Script:Args += "-$k " } } "String" { $Script:Args += "-$k `"$($MyInvocation.BoundParameters[$k])`" " } "Int32" { $Script:Args += "-$k $($MyInvocation.BoundParameters[$k]) " } "Boolean" { $Script:Args += "-$k `$$($MyInvocation.BoundParameters[$k]) " } } } If ($Script:Args) { Start-Process -FilePath "$env:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -ArgumentList "-File `"$($Script:FullName)`" $($Script:Args)" -Wait -NoNewWindow } Else { Start-Process -FilePath "$env:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -ArgumentList "-File `"$($Script:FullName)`"" -Wait -NoNewWindow } } Catch { Throw "Failed to start 64-bit PowerShell" } Exit } [String]$Script:LogDir = "$($env:SystemRoot)\Logs\Software" If (-not(Test-Path -Path $Script:LogDir)) { New-Item -Path $Script:LogDir -ItemType Dir -Force } #endregion Initialization #region Supporting Functions Function Get-InternetFile { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] [uri]$Url, [Parameter(Mandatory = $true, Position = 1)] [string]$OutputDirectory, [Parameter(Mandatory = $false, Position = 2)] [string]$OutputFileName ) Begin { ## Get the name of this function and write header [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name Write-Verbose "Starting ${CmdletName} with the following parameters: $PSBoundParameters" $ProgressPreference = 'SilentlyContinue' } Process { $start_time = Get-Date If (!$OutputFileName) { Write-Verbose "${CmdletName}: No Output File Name specified. Trying to get file name from URL." If ((split-path -path $Url -leaf).Contains('.')) { $OutputFileName = split-path -path $url -leaf Write-Verbose "${CmdletName}: Url contains file name - '$OutputFileName'." } Else { Write-Verbose "${CmdletName}: Url does not contain file name. Trying 'Location' Response Header." $request = [System.Net.WebRequest]::Create($url) $request.AllowAutoRedirect=$false $response=$request.GetResponse() $Location = $response.GetResponseHeader("Location") If ((split-path $Location -leaf) -like '*.*') { $OutputFileName = [System.IO.Path]::GetFileName($Location) Write-Verbose "${CmdletName}: File Name from 'Location' Response Header is '$OutputFileName'." } Else { Write-Verbose "${CmdletName}: No 'Location' Response Header returned. Trying 'Content-Disposition' Response Header." $result = Invoke-WebRequest -Method GET -Uri $Url -UseBasicParsing $contentDisposition = $result.Headers.'Content-Disposition' If ($contentDisposition) { $OutputFileName = $contentDisposition.Split("=")[1].Replace("`"","") Write-Verbose "${CmdletName}: File Name from 'Content-Disposition' Response Header is '$OutputFileName'." } } } } If ($OutputFileName) { $wc = New-Object System.Net.WebClient $OutputFile = Join-Path $OutputDirectory $OutputFileName Write-Verbose "${CmdletName}: Downloading file at '$url' to '$OutputFile'." Try { $wc.DownloadFile($url, $OutputFile) $time = (Get-Date).Subtract($start_time).Seconds Write-Verbose "${CmdletName}: Time taken: '$time' seconds." if (Test-Path -Path $outputfile) { $totalSize = (Get-Item $outputfile).Length / 1MB Write-Verbose "${CmdletName}: Download was successful. Final file size: '$totalsize' mb" Return $OutputFile } } Catch { Write-Error "${CmdletName}: Error downloading file. Please check url." Return $Null } } Else { Write-Error "${CmdletName}: No OutputFileName specified. Unable to download file." Return $Null } } End { Write-Verbose "Ending ${CmdletName}" } } Function Get-InstalledApplication { <# .SYNOPSIS Retrieves information about installed applications. .DESCRIPTION Retrieves information about installed applications by querying the registry. You can specify an application name, a product code, or both. Returns information about application publisher, name & version, product code, uninstall string, install source, location, date, and application architecture. .PARAMETER Name The name of the application to retrieve information for. Performs a contains match on the application display name by default. .PARAMETER Exact Specifies that the named application must be matched using the exact name. .PARAMETER WildCard Specifies that the named application must be matched using a wildcard search. .PARAMETER RegEx Specifies that the named application must be matched using a regular expression search. .PARAMETER ProductCode The product code of the application to retrieve information for. .PARAMETER IncludeUpdatesAndHotfixes Include matches against updates and hotfixes in results. .EXAMPLE Get-InstalledApplication -Name 'Adobe Flash' .EXAMPLE Get-InstalledApplication -ProductCode '{1AD147D0-BE0E-3D6C-AC11-64F6DC4163F1}' #> [CmdletBinding()] Param ( [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [string[]]$Name, [Parameter(Mandatory=$false)] [switch]$Exact = $false, [Parameter(Mandatory=$false)] [switch]$WildCard = $false, [Parameter(Mandatory=$false)] [switch]$RegEx = $false, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [string]$ProductCode, [Parameter(Mandatory=$false)] [switch]$IncludeUpdatesAndHotfixes ) Begin { ## Get the name of this function and write header [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name Write-Verbose "Starting ${CmdletName} with the following parameters: $PSBoundParameters" [string[]]$regKeyApplications = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall' } Process { If ($name) { Write-Verbose "${CmdletName}: Get information for installed Application Name(s) [$($name -join ', ')]..." } If ($productCode) { Write-Verbose "${CmdletName}: Get information for installed Product Code [$ProductCode]..." } ## Enumerate the installed applications from the registry for applications that have the "DisplayName" property [psobject[]]$regKeyApplication = @() ForEach ($regKey in $regKeyApplications) { If (Test-Path -LiteralPath $regKey -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorUninstallKeyPath') { [psobject[]]$UninstallKeyApps = Get-ChildItem -LiteralPath $regKey -ErrorAction 'SilentlyContinue' -ErrorVariable '+ErrorUninstallKeyPath' ForEach ($UninstallKeyApp in $UninstallKeyApps) { Try { [psobject]$regKeyApplicationProps = Get-ItemProperty -LiteralPath $UninstallKeyApp.PSPath -ErrorAction 'Stop' If ($regKeyApplicationProps.DisplayName) { [psobject[]]$regKeyApplication += $regKeyApplicationProps } } Catch{ Write-Warning "${CmdletName}: Unable to enumerate properties from registry key path [$($UninstallKeyApp.PSPath)]." Continue } } } } If ($ErrorUninstallKeyPath) { Write-Warning "${CmdletName}: The following error(s) took place while enumerating installed applications from the registry." } $UpdatesSkippedCounter = 0 ## Create a custom object with the desired properties for the installed applications and sanitize property details [psobject[]]$installedApplication = @() ForEach ($regKeyApp in $regKeyApplication) { Try { [string]$appDisplayName = '' [string]$appDisplayVersion = '' [string]$appPublisher = '' ## Bypass any updates or hotfixes If ((-not $IncludeUpdatesAndHotfixes) -and (($regKeyApp.DisplayName -match '(?i)kb\d+') -or ($regKeyApp.DisplayName -match 'Cumulative Update') -or ($regKeyApp.DisplayName -match 'Security Update') -or ($regKeyApp.DisplayName -match 'Hotfix'))) { $UpdatesSkippedCounter += 1 Continue } ## Remove any control characters which may interfere with logging and creating file path names from these variables $appDisplayName = $regKeyApp.DisplayName -replace '[^\u001F-\u007F]','' $appDisplayVersion = $regKeyApp.DisplayVersion -replace '[^\u001F-\u007F]','' $appPublisher = $regKeyApp.Publisher -replace '[^\u001F-\u007F]','' ## Determine if application is a 64-bit application [boolean]$Is64BitApp = If (($is64Bit) -and ($regKeyApp.PSPath -notmatch '^Microsoft\.PowerShell\.Core\\Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node')) { $true } Else { $false } If ($ProductCode) { ## Verify if there is a match with the product code passed to the script If ($regKeyApp.PSChildName -match [regex]::Escape($productCode)) { Write-Verbose "${CmdletName}:Found installed application [$appDisplayName] version [$appDisplayVersion] matching product code [$productCode]." $installedApplication += New-Object -TypeName 'PSObject' -Property @{ UninstallSubkey = $regKeyApp.PSChildName ProductCode = If ($regKeyApp.PSChildName -match $MSIProductCodeRegExPattern) { $regKeyApp.PSChildName } Else { [string]::Empty } DisplayName = $appDisplayName DisplayVersion = $appDisplayVersion UninstallString = $regKeyApp.UninstallString InstallSource = $regKeyApp.InstallSource InstallLocation = $regKeyApp.InstallLocation InstallDate = $regKeyApp.InstallDate Publisher = $appPublisher Is64BitApplication = $Is64BitApp } } } If ($name) { ## Verify if there is a match with the application name(s) passed to the script ForEach ($application in $Name) { $applicationMatched = $false If ($exact) { # Check for an exact application name match If ($regKeyApp.DisplayName -eq $application) { $applicationMatched = $true Write-Verbose "${CmdletName}: Found installed application [$appDisplayName] version [$appDisplayVersion] using exact name matching for search term [$application]." } } ElseIf ($WildCard) { # Check for wildcard application name match If ($regKeyApp.DisplayName -like $application) { $applicationMatched = $true Write-Verbose "${CmdletName}: Found installed application [$appDisplayName] version [$appDisplayVersion] using wildcard matching for search term [$application]." } } ElseIf ($RegEx) { # Check for a regex application name match If ($regKeyApp.DisplayName -match $application) { $applicationMatched = $true Write-Verbose "${CmdletName}: Found installed application [$appDisplayName] version [$appDisplayVersion] using regex matching for search term [$application]." } } # Check for a contains application name match ElseIf ($regKeyApp.DisplayName -match [regex]::Escape($application)) { $applicationMatched = $true Write-Verbose "${CmdletName}: Found installed application [$appDisplayName] version [$appDisplayVersion] using contains matching for search term [$application]." } If ($applicationMatched) { $installedApplication += New-Object -TypeName 'PSObject' -Property @{ UninstallSubkey = $regKeyApp.PSChildName ProductCode = If ($regKeyApp.PSChildName -match $MSIProductCodeRegExPattern) { $regKeyApp.PSChildName } Else { [string]::Empty } DisplayName = $appDisplayName DisplayVersion = $appDisplayVersion UninstallString = $regKeyApp.UninstallString InstallSource = $regKeyApp.InstallSource InstallLocation = $regKeyApp.InstallLocation InstallDate = $regKeyApp.InstallDate Publisher = $appPublisher Is64BitApplication = $Is64BitApp } } } } } Catch { Write-Error "${CmdletName}: Failed to resolve application details from registry for [$appDisplayName]." Continue } } If (-not $IncludeUpdatesAndHotfixes) { ## Write to log the number of entries skipped due to them being considered updates If ($UpdatesSkippedCounter -eq 1) { Write-Verbose "${CmdletName}: Skipped 1 entry while searching, because it was considered a Microsoft update." } else { Write-Verbose "${CmdletName}: Skipped $UpdatesSkippedCounter entries while searching, because they were considered Microsoft updates." } } If (-not $installedApplication) { Write-Verbose "${CmdletName}: Found no application based on the supplied parameters." } Write-Output -InputObject $installedApplication } End { Write-Verbose "Ending ${CmdletName}" } } function Get-MsiInfo { <# .SYNOPSIS Queries parameter information from one or more MSI files .DESCRIPTION By default will return the ProductCode,ProductVersion,ProductName,Manufacturer,ProductLanguage,FullVersion. If an empty string is provided for the Property parameter, then all properties are returned .PARAMETER Path MSI Path(s) provided either explicitly or from the pipeline .PARAMETER Property The names of the MSI properties to return. Specify empty string to return all properties .EXAMPLE gci *.msi | Get-MsiInfo -Property 'ProductName','ProductVersion','Manufacturer' -------------------- Gets specific properties for all MSIs in the current directory .EXAMPLE gci *.msi | Get-MsiInfo -------------------- Get all properties for all MSIs in the current directory #> [CmdletBinding()] param( [parameter(Mandatory=$True, ValueFromPipeline=$true)] [IO.FileInfo[]]$Path, [AllowEmptyString()] [AllowNull()] [string[]]$Property ) Begin { ## Get the name of this function and write header [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name Write-Verbose -Message "Starting ${CmdletName}." $winInstaller = New-Object -ComObject WindowsInstaller.Installer } Process { try { #Write-Log -Message "Opening MSIFile: $Path" -Source ${CmdletName} $msiDb = $winInstaller.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $null, $winInstaller, @($Path.FullName, 0)) if($Property) { Write-Verbose -Message "${CmdletName}: Property: $Property specified" $propQuery = 'WHERE ' + (($Property | ForEach-Object { "Property = '$($_)'"}) -join ' OR ') } $query = ("SELECT Property,Value FROM Property {0}" -f ($propQuery)) $view = $msiDb.GetType().InvokeMember('OpenView', 'InvokeMethod', $null, $msiDb, ($query)) $null = $view.GetType().InvokeMember('Execute', 'InvokeMethod', $null, $view, $null) $msiInfo = [PSCustomObject]@{'File' = $Path} do { $null = $view.GetType().InvokeMember('ColumnInfo', 'GetProperty', $null, $view, 0) $record = $view.GetType().InvokeMember('Fetch', 'InvokeMethod', $null, $view, $null) if(-not $record) { break; } $propName = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 1) | select-object -First 1 $value = $record.GetType().InvokeMember('StringData', 'GetProperty', $null, $record, 2) | select-object -First 1 $msiInfo = $msiInfo | Add-Member -MemberType NoteProperty -Name $propName -Value $value -PassThru } while ($true) $null = $msiDb.GetType().InvokeMember('Commit', 'InvokeMethod', $null, $msiDb, $null) $null = $view.GetType().InvokeMember('Close', 'InvokeMethod', $null, $view, $null) Write-Verbose -Message "${CmdletName}: Returning information about msi file." $msiInfo } catch { Write-Warning -Message "${CmdletName}: $_" Write-Warning -Message "${CmdletName}: $($_.ScriptStackTrace)" } } End { try { $null = [Runtime.Interopservices.Marshal]::ReleaseComObject($winInstaller) [GC]::Collect() } catch { Write-Warning -Message "${CmdletName}: Failed to release Windows Installer COM reference" Write-Warning -Message "${CmdletName}: " + $_ } Write-Verbose -Message "Ending ${CmdletName}" } } Function Remove-MSIApplications { <# .SYNOPSIS Removes all MSI applications matching the specified application name. .DESCRIPTION Removes all MSI applications matching the specified application name. Enumerates the registry for installed applications matching the specified application name and uninstalls that application using the product code, provided the uninstall string matches "msiexec". .PARAMETER Name The name of the application to uninstall. Performs a contains match on the application display name by default. .PARAMETER Exact Specifies that the named application must be matched using the exact name. .PARAMETER WildCard Specifies that the named application must be matched using a wildcard search. .PARAMETER Parameters Overrides the default parameters specified in the XML configuration file. Uninstall default is: "REBOOT=ReallySuppress /QN". .PARAMETER AddParameters Adds to the default parameters specified in the XML configuration file. Uninstall default is: "REBOOT=ReallySuppress /QN". .PARAMETER FilterApplication Two-dimensional array that contains one or more (property, value, match-type) sets that should be used to filter the list of results returned by Get-InstalledApplication to only those that should be uninstalled. Properties that can be filtered upon: ProductCode, DisplayName, DisplayVersion, UninstallString, InstallSource, InstallLocation, InstallDate, Publisher, Is64BitApplication .PARAMETER ExcludeFromUninstall Two-dimensional array that contains one or more (property, value, match-type) sets that should be excluded from uninstall if found. Properties that can be excluded: ProductCode, DisplayName, DisplayVersion, UninstallString, InstallSource, InstallLocation, InstallDate, Publisher, Is64BitApplication .PARAMETER IncludeUpdatesAndHotfixes Include matches against updates and hotfixes in results. .PARAMETER LoggingOptions Overrides the default logging options specified in the XML configuration file. Default options are: "/L*v". .PARAMETER LogName Overrides the default log file name. The default log file name is generated from the MSI file name. If LogName does not end in .log, it will be automatically appended. For uninstallations, by default the product code is resolved to the DisplayName and version of the application. .PARAMETER PassThru Returns ExitCode, STDOut, and STDErr output from the process. .PARAMETER ContinueOnError Continue if an error occured while trying to start the processes. Default: $true. .EXAMPLE Remove-MSIApplications -Name 'Adobe Flash' Removes all versions of software that match the name "Adobe Flash" .EXAMPLE Remove-MSIApplications -Name 'Adobe' Removes all versions of software that match the name "Adobe" .EXAMPLE Remove-MSIApplications -Name 'Java 8 Update' -FilterApplication ('Is64BitApplication', $false, 'Exact'),('Publisher', 'Oracle Corporation', 'Exact') Removes all versions of software that match the name "Java 8 Update" where the software is 32-bits and the publisher is "Oracle Corporation". .EXAMPLE Remove-MSIApplications -Name 'Java 8 Update' -FilterApplication (,('Publisher', 'Oracle Corporation', 'Exact')) -ExcludeFromUninstall (,('DisplayName', 'Java 8 Update 45', 'Contains')) Removes all versions of software that match the name "Java 8 Update" and also have "Oracle Corporation" as the Publisher; however, it does not uninstall "Java 8 Update 45" of the software. NOTE: if only specifying a single row in the two-dimensional arrays, the array must have the extra parentheses and leading comma as in this example. .EXAMPLE Remove-MSIApplications -Name 'Java 8 Update' -ExcludeFromUninstall (,('DisplayName', 'Java 8 Update 45', 'Contains')) Removes all versions of software that match the name "Java 8 Update"; however, it does not uninstall "Java 8 Update 45" of the software. NOTE: if only specifying a single row in the two-dimensional array, the array must have the extra parentheses and leading comma as in this example. .EXAMPLE Remove-MSIApplications -Name 'Java 8 Update' -ExcludeFromUninstall ('Is64BitApplication', $true, 'Exact'), ('DisplayName', 'Java 8 Update 45', 'Exact'), ('DisplayName', 'Java 8 Update 4*', 'WildCard'), ('DisplayName', 'Java \d Update \d{3}', 'RegEx'), ('DisplayName', 'Java 8 Update', 'Contains') Removes all versions of software that match the name "Java 8 Update"; however, it does not uninstall 64-bit versions of the software, Update 45 of the software, or any Update that starts with 4. .NOTES More reading on how to create arrays if having trouble with -FilterApplication or -ExcludeFromUninstall parameter: http://blogs.msdn.com/b/powershell/archive/2007/01/23/array-literals-in-powershell.aspx #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$Name, [Parameter(Mandatory=$false)] [switch]$Exact = $false, [Parameter(Mandatory=$false)] [switch]$WildCard = $false, [Parameter(Mandatory=$false)] [Alias('Arguments')] [ValidateNotNullorEmpty()] [string]$Parameters, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [string]$AddParameters, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [array]$FilterApplication = @(@()), [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [array]$ExcludeFromUninstall = @(@()), [Parameter(Mandatory=$false)] [switch]$IncludeUpdatesAndHotfixes = $false, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [string]$LoggingOptions, [Parameter(Mandatory=$false)] [Alias('LogName')] [string]$private:LogName, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [switch]$PassThru = $false, [Parameter(Mandatory=$false)] [ValidateNotNullorEmpty()] [boolean]$ContinueOnError = $true ) Begin { ## Get the name of this function and write header [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name Write-Verbose "Starting ${CmdLetName} with the following parameters: $PSBoundParameters" } Process { ## Build the hashtable with the options that will be passed to Get-InstalledApplication using splatting [hashtable]$GetInstalledApplicationSplat = @{ Name = $name } If ($Exact) { $GetInstalledApplicationSplat.Add( 'Exact', $Exact) } ElseIf ($WildCard) { $GetInstalledApplicationSplat.Add( 'WildCard', $WildCard) } If ($IncludeUpdatesAndHotfixes) { $GetInstalledApplicationSplat.Add( 'IncludeUpdatesAndHotfixes', $IncludeUpdatesAndHotfixes) } [psobject[]]$installedApplications = Get-InstalledApplication @GetInstalledApplicationSplat Write-Verbose "${CmdLetName}: Found [$($installedApplications.Count)] application(s) that matched the specified criteria [$Name]." ## Filter the results from Get-InstalledApplication [Collections.ArrayList]$removeMSIApplications = New-Object -TypeName 'System.Collections.ArrayList' If (($null -ne $installedApplications) -and ($installedApplications.Count)) { ForEach ($installedApplication in $installedApplications) { If ([string]::IsNullOrEmpty($installedApplication.ProductCode)) { Write-Warning "${CmdletName}: Skipping removal of application [$($installedApplication.DisplayName)] because unable to discover MSI ProductCode from application's registry Uninstall subkey [$($installedApplication.UninstallSubkey)]." Continue } # Filter the results from Get-InstalledApplication to only those that should be uninstalled If (($null -ne $FilterApplication) -and ($FilterApplication.Count)) { Write-Verbose "${CmdletName}: Filter the results to only those that should be uninstalled as specified in parameter [-FilterApplication]." [boolean]$addAppToRemoveList = $false ForEach ($Filter in $FilterApplication) { If ($Filter[2] -eq 'RegEx') { If ($installedApplication.($Filter[0]) -match $Filter[1]) { [boolean]$addAppToRemoveList = $true Write-Verbose "${CmdletName}: Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of regex match against [-FilterApplication] criteria." } } ElseIf ($Filter[2] -eq 'Contains') { If ($installedApplication.($Filter[0]) -match [regex]::Escape($Filter[1])) { [boolean]$addAppToRemoveList = $true Write-Verbose "${CmdletName}: Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of contains match against [-FilterApplication] criteria." } } ElseIf ($Filter[2] -eq 'WildCard') { If ($installedApplication.($Filter[0]) -like $Filter[1]) { [boolean]$addAppToRemoveList = $true Write-Verbose "${CmdletName}: Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of wildcard match against [-FilterApplication] criteria." } } ElseIf ($Filter[2] -eq 'Exact') { If ($installedApplication.($Filter[0]) -eq $Filter[1]) { [boolean]$addAppToRemoveList = $true Write-Verbose "${CmdletName}: Preserve removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of exact match against [-FilterApplication] criteria." } } } } Else { [boolean]$addAppToRemoveList = $true } # Filter the results from Get-InstalledApplication to remove those that should never be uninstalled If (($null -ne $ExcludeFromUninstall) -and ($ExcludeFromUninstall.Count)) { ForEach ($Exclude in $ExcludeFromUninstall) { If ($Exclude[2] -eq 'RegEx') { If ($installedApplication.($Exclude[0]) -match $Exclude[1]) { [boolean]$addAppToRemoveList = $false Write-Verbose "${CmdletName}: Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of regex match against [-ExcludeFromUninstall] criteria." } } ElseIf ($Exclude[2] -eq 'Contains') { If ($installedApplication.($Exclude[0]) -match [regex]::Escape($Exclude[1])) { [boolean]$addAppToRemoveList = $false Write-Verbose "${CmdletName}: Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of contains match against [-ExcludeFromUninstall] criteria." } } ElseIf ($Exclude[2] -eq 'WildCard') { If ($installedApplication.($Exclude[0]) -like $Exclude[1]) { [boolean]$addAppToRemoveList = $false Write-Verbose "${CmdletName}: Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of wildcard match against [-ExcludeFromUninstall] criteria." } } ElseIf ($Exclude[2] -eq 'Exact') { If ($installedApplication.($Exclude[0]) -eq $Exclude[1]) { [boolean]$addAppToRemoveList = $false Write-Verbose "${CmdletName}: Skipping removal of application [$($installedApplication.DisplayName) $($installedApplication.Version)] because of exact match against [-ExcludeFromUninstall] criteria." } } } } If ($addAppToRemoveList) { Write-Verbose "${CmdletName}: Adding application to list for removal: [$($installedApplication.DisplayName) $($installedApplication.Version)]." $removeMSIApplications.Add($installedApplication) } } } ## Build the hashtable with the options that will be passed to Execute-MSI using splatting [hashtable]$ExecuteMSISplat = @{ Action = 'Uninstall' Path = '' ContinueOnError = $ContinueOnError } If ($Parameters) { $ExecuteMSISplat.Add( 'Parameters', $Parameters) } ElseIf ($AddParameters) { $ExecuteMSISplat.Add( 'AddParameters', $AddParameters) } If ($LoggingOptions) { $ExecuteMSISplat.Add( 'LoggingOptions', $LoggingOptions) } If ($LogName) { $ExecuteMSISplat.Add( 'LogName', $LogName) } If ($PassThru) { $ExecuteMSISplat.Add( 'PassThru', $PassThru) } If ($IncludeUpdatesAndHotfixes) { $ExecuteMSISplat.Add( 'IncludeUpdatesAndHotfixes', $IncludeUpdatesAndHotfixes) } If (($null -ne $removeMSIApplications) -and ($removeMSIApplications.Count)) { ForEach ($removeMSIApplication in $removeMSIApplications) { Write-Verbose "${CmdletName}: Remove application [$($removeMSIApplication.DisplayName) $($removeMSIApplication.Version)]." $ExecuteMSISplat.Path = $removeMSIApplication.ProductCode If ($PassThru) { [psobject[]]$ExecuteResults += Execute-MSI @ExecuteMSISplat } Else { Execute-MSI @ExecuteMSISplat } } } Else { Write-Log -Message 'No applications found for removal. Continue...' } } End { If ($PassThru) { Write-Output -InputObject $ExecuteResults } Write-Verbose "Ending ${CmdletName}." } } #endregion ## MAIN If ($DeploymentType -ne 'UnInstall') { [string]$Script:LogName = "Install-" + ($SoftwareName -Replace ' ','') + ".log" Start-Transcript -Path "$Script:LogDir\$Script:LogName" -Force Write-Output "Retrieving latest $SoftwareName version from Internet." $pathMSI = Get-InternetFile -url $downloadUrl -OutputDirectory $env:Temp [version]$AvailableVersion = (Get-MsiInfo -Path $pathMSI).ProductVersion Write-Output "Determining if '$softwareName' is installed and if so, what version." $Installed = Get-InstalledApplication -Name $SoftwareName If ($Installed) { [version]$InstalledVersion = $Installed.DisplayVersion Write-Output "Version '$InstalledVersion' of '$SoftwareName' installed." } Else { Write-Output "'$softwareName' is not installed." } If (-not($Installed) -or $AvailableVersion -gt $InstalledVersion) { Write-Output "Installing '$SoftwareName' via cmdline:" Write-Output " 'msiexec.exe /i `"$pathMSI`" $MSIArguments'" $Installer = Start-Process -FilePath 'msiexec.exe' -ArgumentList "/i `"$pathMSI`" $MSIArguments" -Wait -PassThru If ($($Installer.ExitCode) -eq 0) { Write-Output "'$SoftwareName' installed successfully." Remove-Item -Path $pathMSI -Force -ErrorAction SilentlyContinue } Else { Write-Error "The Installer exit code is $($Installer.ExitCode)" } Write-Output "Completed '$SoftwareName' Installation." } Else { Write-Output "'$SoftwareName' is already installed and is the current version." } $null = cmd /c REG.exe ADD HKLM\SOFTWARE\Microsoft\MSRDC\Policies /v AutomaticUpdates /d 0 /t REG_DWORD /f '2>&1' } # Uninstall Else { [string]$Script:LogName = "UnInstall-" + ($SoftwareName -Replace ' ','') + ".log" Start-Transcript -Path "$Script:LogDir\$Script:LogName" -Force Write-Output "Removing $SoftwareName" Remove-MSIApplications -Name $SoftwareName -verbose } Stop-Transcript