OfficeDsc.psm1
$ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest if ([string]::IsNullOrEmpty($env:TestRegistryPath)) { $global:OfficeRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' $global:OfficeGroupPolicyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Office\16.0\Common\OfficeUpdate' $global:OfficeProductReleaseIdsPath = 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIds' } else { $global:OfficeRegistryPath = $global:OfficeRegistryPath = $global:OfficeProductReleaseIdsPath = $env:TestRegistryPath } # Supported Office language IDs: https://learn.microsoft.com/en-us/microsoft-365-apps/deploy/overview-deploying-languages-microsoft-365-apps#languages-culture-codes-and-companion-proofing-languages $global:supportedLanguages = @( 'af-ZA', 'sq-AL', 'ar-SA', 'hy-AM', 'as-IN', 'az-Latn-AZ', 'bn-BD', 'bn-IN', 'eu-ES', 'bs-latn-BA', 'bg-BG', 'ca-ES', 'ca-ES-valencia', 'zh-CN', 'zh-TW', 'hr-HR', 'cs-CZ', 'da-DK', 'nl-NL', 'en-US', 'en-GB', 'et-EE', 'fi-FI', 'fr-FR', 'fr-CA', 'gl-ES', 'ka-GE', 'de-DE', 'el-GR', 'gu-IN', 'ha-Latn-NG', 'he-IL', 'hi-IN', 'hu-HU', 'is-IS', 'ig-NG', 'id-ID', 'ga-IE', 'xh-ZA', 'zu-ZA', 'it-IT', 'ja-JP', 'kn-IN', 'kk-KZ', 'rw-RW', 'sw-KE', 'kok-IN', 'ko-KR', 'ky-KG', 'lv-LV', 'lt-LT', 'lb-LU', 'mk-MK', 'ms-MY', 'ml-IN', 'mt-MT', 'mi-NZ', 'mr-IN', 'ne-NP', 'nb-NO', 'nn-NO', 'or-IN', 'ps-AF', 'fa-IR', 'pl-PL', 'pt-PT', 'pt-BR', 'pa-IN', 'ro-RO', 'rm-CH', 'ru-RU', 'gd-GB', 'sr-cyrl-RS', 'sr-latn-RS', 'sr-cyrl-BA', 'nso-ZA', 'tn-ZA', 'si-LK', 'sk-SK', 'sl-SI', 'es-ES', 'es-MX', 'sv-SE', 'ta-IN', 'tt-RU', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA', 'ur-PK', 'uz-Latn-UZ', 'vi-VN', 'cy-GB', 'wo-SN', 'yo-NG' ) #region Enums # ProductId enumeration: https://learn.microsoft.com/en-us/troubleshoot/microsoft-365-apps/office-suite-issues/product-ids-supported-office-deployment-click-to-run enum ProductId { O365ProPlusEEANoTeamsRetail # Microsoft 365 Apps for enterprise O365ProPlusRetail # Office 365 Enterprise E3, E5, Microsoft 365 E3, E5, Office 365 E3, E5 O365BusinessEEANoTeamsRetail # Microsoft 365 Apps for business O365BusinessRetail # Microsoft 365 Business Standard, Business Premium } enum PackageId { Access Excel Groove Lync OneDrive OneNote Outlook OutlookForWindows PowerPoint Publisher Teams Word } # Channel enumeration: https://learn.microsoft.com/en-us/microsoft-365-apps/deploy/office-deployment-tool-configuration-options#channel-attribute-part-of-add-element enum Channel { BetaChannel CurrentPreview Current MonthlyEnterprise SemiAnnualPreview SemiAnnual } #endregion Enums #region Functions function Get-OfficeGroupPolicyChannelSetting { [OutputType([Channel])] [CmdletBinding()] param ( ) # Registry key found: https://learn.microsoft.com/en-us/troubleshoot/microsoft-365-apps/installation/automatic-updates#resolution $channelUri = TryGetRegistryValue -Key $global:OfficeGroupPolicyPath -Property 'updatebranch' if ([string]::IsNullOrEmpty($channelUri)) { Write-Verbose -Message 'Group policy is not set, using local channel setting.' return Get-OfficeChannel } # Extra check if Group Policy is setting a different channel switch ($channelUri) { 'InsiderFast' { return [Channel]::BetaChannel } 'FirstReleaseCurrent' { return [Channel]::CurrentPreview } 'Current' { return [Channel]::Current } 'MonthlyEnterprise' { return [Channel]::MonthlyEnterprise } 'FirstReleaseDeferred' { return [Channel]::SemiAnnualPreview } 'Deferred' { return [Channel]::SemiAnnual } default { throw "Unknown channel value found in Group Policy: '$channelUri'" } } } function Get-OfficeChannel { [OutputType([Channel])] [CmdletBinding()] param ( ) $Uri = TryGetRegistryValue -Key $global:OfficeRegistryPath -Property 'UpdateChannel' if ([string]::IsNullOrEmpty($Uri)) { Write-Verbose -Message 'No channel URI found in registry, defaulting to Current channel.' return [Channel]::Current } # Channel URIs: https://learn.microsoft.com/en-us/intune/intune-service/configuration/settings-catalog-update-office#check-the-intune-registry-keys $Channel = switch ($Uri) { 'http://officecdn.microsoft.com/pr/5440fd1f-7ecb-4221-8110-145efaa6372f' { [Channel]::BetaChannel } 'http://officecdn.microsoft.com/pr/64256afe-f5d9-4f86-8936-8840a6a4f5be' { [Channel]::CurrentPreview } 'http://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6ddf67d60' { [Channel]::Current } 'http://officecdn.microsoft.com/pr/55336b82-a18d-4dd6-b5f6-9e5095c314a6' { [Channel]::MonthlyEnterprise } 'http://officecdn.microsoft.com/pr/b8f9b850-328d-4355-9145-c59439a0c4cf' { [Channel]::SemiAnnualPreview } 'http://officecdn.microsoft.com/pr/7ffbc6bf-bc32-4f92-8982-f9dd17fd3114' { [Channel]::SemiAnnual } default { throw "Unknown channel URI found in registry: '$Uri'" } } return $Channel } function Get-OfficeInstallation { [OutputType([System.Collections.Hashtable])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ProductId]$ProductId ) # find the known key $keyPresent = TryGetRegistryValue -Key $global:OfficeRegistryPath -Property 'InstallationPath' # extra check if the product is installed via Click-to-Run $installed = $false if ($null -ne $keyPresent) { $installed = Test-Path -Path $keyPresent -ErrorAction Ignore } $searchProperty = [System.String]::Concat($ProductId, '.ExcludedApps') # go through the excluded apps and filter out the installed apps Write-Verbose -Message "Searching for excluded apps with property name: '$searchProperty'." $excludedApps = TryGetRegistryValue -Key $global:OfficeRegistryPath -Property $searchProperty $appsInstalled = [PackageId]::GetNames([PackageId]) $excludedAppsArray = @() if ($null -ne $excludedApps) { $textInfo = [System.Globalization.CultureInfo]::CurrentCulture.TextInfo $excludedAppsArray = ($excludedApps.Split(',') | ForEach-Object { $textInfo.ToTitleCase($_.Trim()) }) $appsInstalled = $appsInstalled | Where-Object { $_ -notin $excludedAppsArray } } return @{ Installed = $installed Apps = $appsInstalled ExcludedApps = ($null -ne $excludedAppsArray) ? $excludedAppsArray : @() # Nothing was excluded ProductId = $ProductId } } function Assert-OfficeDeploymentToolSetup { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Path ) try { if (-not (Test-Path -Path $Path -PathType Leaf)) { throw "The specified path '$Path' does not exist." } if ([System.IO.Path]::GetExtension($Path) -ne '.exe') { throw "The specified path '$Path' is not an executable file." } # Run setup.exe with '/?' to verify it is the Office Deployment Tool $output = & $Path '/?' 2>&1 if ($LASTEXITCODE -ne 0) { throw "The executable at '$Path' did not exit successfully. ExitCode: $LASTEXITCODE" } # TODO: Can be improved by checking for specific output lines if ($output[1] -ne 'Office Deployment Tool') { throw "The executable at '$Path' does not appear to be the Office Deployment Tool." } } catch { throw "Failed to validate Office Deployment Tool setup: $($_.Exception.Message)" } } <# .SYNOPSIS Creates an Office Deployment Tool configuration XML file for installation. .DESCRIPTION Generates a configuration XML file for the Office Deployment Tool (ODT) to install Microsoft Office products. The XML includes product specifications, languages, excluded applications, and display settings. .PARAMETER ProductId The Office product identifier to install. .PARAMETER ExcludeApps Array of Office applications to exclude from installation. .PARAMETER Channel The Office update channel to use. Default is Current channel. .PARAMETER LanguageId Array of language identifiers to install. Default is the current system culture. .INPUTS None This function does not accept pipeline input. .OUTPUTS System.String Path to the temporary configuration XML file. .EXAMPLE New-OfficeInstallationConfigurationFile -ProductId O365ProPlusRetail Creates installation configuration for Office 365 Pro Plus with current system language. .EXAMPLE New-OfficeInstallationConfigurationFile -ProductId O365ProPlusRetail -ExcludeApps @('Teams', 'OneNote') -LanguageId @('en-US', 'fr-FR') Creates installation configuration excluding Teams and OneNote with English and French languages. #> function New-OfficeInstallationConfigurationFile { [OutputType([System.String])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ProductId] $ProductId, [Parameter()] [System.String[]] $ExcludeApps = @(), [Parameter()] [Channel] $Channel = [Channel]::Current, [Parameter()] [System.String[]] $LanguageId ) if ([string]::IsNullOrEmpty($LanguageId)) { $currentCulture = [System.Globalization.CultureInfo]::CurrentCulture.Name Write-Verbose -Message "No LanguageId specified, defaulting to current system culture: '$currentCulture'." $LanguageId = @($currentCulture) } $bitness = [Environment]::Is64BitOperatingSystem ? '64' : '32' $xml = [System.Xml.XmlDocument]::new() $configuration = $xml.CreateElement('Configuration') $xml.AppendChild($configuration) | Out-Null $addNode = $xml.CreateElement('Add') $addNode.SetAttribute('OfficeClientEdition', $bitness) $addNode.SetAttribute('Channel', $Channel) $configuration.AppendChild($addNode) | Out-Null $product = $xml.CreateElement('Product') $product.SetAttribute('ID', $ProductId) $addNode.AppendChild($product) | Out-Null foreach ($languageCode in $LanguageId) { $language = $xml.CreateElement('Language') $language.SetAttribute('ID', $languageCode) $product.AppendChild($language) | Out-Null } foreach ($applicationId in $ExcludeApps) { $excludeApplication = $xml.CreateElement('ExcludeApp') $excludeApplication.SetAttribute('ID', $applicationId) $product.AppendChild($excludeApplication) | Out-Null } $display = $xml.CreateElement('Display') $display.SetAttribute('Level', 'None') $display.SetAttribute('AcceptEULA', 'TRUE') $configuration.AppendChild($display) | Out-Null $stringWriter = [System.IO.StringWriter]::new() $xmlWriter = [System.Xml.XmlTextWriter]::new($stringWriter) $xmlWriter.Formatting = 'Indented' $xml.WriteTo($xmlWriter) $xmlWriter.Flush() $configurationXml = $stringWriter.ToString() $xmlWriter.Close() Write-Verbose -Message "Generated Office installation configuration XML:`n$configurationXml" $tempFilePath = Join-Path ([System.IO.Path]::GetTempPath()) "ODT_Install_$(Get-Random).xml" try { Set-Content -Path $tempFilePath -Value $configurationXml -Encoding UTF8 -Force -ErrorAction Stop Write-Verbose -Message "Temporary configuration file created at: '$tempFilePath'." return $tempFilePath } catch { Write-Error -Message "Failed to create temporary configuration file: '$tempFilePath'" -Category WriteError -ErrorId 'TempFileCreationFailed' -TargetObject $tempFilePath -Exception $_.Exception return $null } } <# .SYNOPSIS Creates an Office Deployment Tool configuration XML file for removal. .DESCRIPTION Generates a configuration XML file for the Office Deployment Tool (ODT) to remove Microsoft Office products. If no LanguageId is specified, sets All="TRUE" to remove all Office products and languages. If LanguageId is specified, sets All="FALSE" for targeted removal of specific languages. .PARAMETER ProductId The Office product identifier to remove. .PARAMETER LanguageId Array of language identifiers to remove. If not specified, removes all Office products and languages (All="TRUE"). If specified, performs targeted removal (All="FALSE"). .INPUTS None This function does not accept pipeline input. .OUTPUTS System.String Path to the temporary configuration XML file. .EXAMPLE New-OfficeRemovalConfigurationFile -ProductId O365ProPlusRetail Creates removal configuration that removes all Office products and languages (All="TRUE"). .EXAMPLE New-OfficeRemovalConfigurationFile -ProductId O365ProPlusRetail -LanguageId @('en-US', 'fr-FR') Creates removal configuration for specific languages with targeted removal (All="FALSE"). #> function New-OfficeRemovalConfigurationFile { [OutputType([System.String])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ProductId] $ProductId, [Parameter()] [System.String[]] $LanguageId ) $bitness = [Environment]::Is64BitOperatingSystem ? '64' : '32' $xml = [System.Xml.XmlDocument]::new() $configuration = $xml.CreateElement('Configuration') $xml.AppendChild($configuration) | Out-Null $removeNode = $xml.CreateElement('Remove') $removeNode.SetAttribute('OfficeClientEdition', $bitness) if ($null -eq $LanguageId -or $LanguageId.Count -eq 0) { $removeNode.SetAttribute('All', 'TRUE') Write-Verbose -Message 'No LanguageId specified for removal. Setting All="TRUE" to remove all Office products and languages.' } else { $removeNode.SetAttribute('All', 'FALSE') Write-Verbose -Message 'LanguageId specified for removal. Setting All="FALSE" for targeted removal.' $product = $xml.CreateElement('Product') $product.SetAttribute('ID', $ProductId) $removeNode.AppendChild($product) | Out-Null foreach ($languageCode in $LanguageId) { $language = $xml.CreateElement('Language') $language.SetAttribute('ID', $languageCode) $product.AppendChild($language) | Out-Null } } $configuration.AppendChild($removeNode) | Out-Null $display = $xml.CreateElement('Display') $display.SetAttribute('Level', 'None') $configuration.AppendChild($display) | Out-Null $stringWriter = [System.IO.StringWriter]::new() $xmlWriter = [System.Xml.XmlTextWriter]::new($stringWriter) $xmlWriter.Formatting = 'Indented' $xml.WriteTo($xmlWriter) $xmlWriter.Flush() $configurationXml = $stringWriter.ToString() $xmlWriter.Close() Write-Verbose -Message "Generated Office removal configuration XML:`n$configurationXml" $tempFilePath = Join-Path ([System.IO.Path]::GetTempPath()) "ODT_Remove_$(Get-Random).xml" try { Set-Content -Path $tempFilePath -Value $configurationXml -Encoding UTF8 -Force -ErrorAction Stop Write-Verbose -Message "Temporary configuration file created at: '$tempFilePath'." return $tempFilePath } catch { Write-Error -Message "Failed to create temporary configuration file: '$tempFilePath'" -Category WriteError -ErrorId 'TempFileCreationFailed' -TargetObject $tempFilePath -Exception $_.Exception return $null } } function Assert-LanguageInstalled { [CmdletBinding()] param ( [Parameter(Mandatory)] [string[]] $LanguageId ) $installedLanguages = (Get-CimInstance Win32_OperatingSystem).MUILanguages $missing = $LanguageId | Where-Object { $_ -notin $installedLanguages } if ($missing) { throw "The following languages are not installed on the system: $($missing -join ', ')" } } function Assert-OfficeLanguageSupported { [CmdletBinding()] param ( [Parameter(Mandatory)] [string[]] $LanguageId ) $unsupported = $LanguageId | Where-Object { $_ -notin $global:supportedLanguages } if ($unsupported) { throw "The following languages are not supported by Office: $($unsupported -join ', ')" } } function Test-SupportedLanguageId { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$LanguageId ) return ($LanguageId | ForEach-Object { $global:supportedLanguages -contains $_ }) -notcontains $false } function Install-OfficeProduct { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $Path, [Parameter(Mandatory)] [ProductId] $ProductId, [Parameter()] [Channel] $Channel = [Channel]::Current, [Parameter()] [AllowNull()] [string[]] $LanguageId, [Parameter()] [PackageId[]] $ExcludeApps = @() ) $configurationFileParameters = @{ ProductId = $ProductId Channel = $Channel LanguageId = $LanguageId ExcludeApps = $ExcludeApps } $configurationFilePath = New-OfficeInstallationConfigurationFile @configurationFileParameters if (-not ([string]::IsNullOrEmpty($LanguageId))) { Write-Verbose -Message "Validating specified LanguageId(s): $($LanguageId -join ', ')" Assert-LanguageInstalled -LanguageId $LanguageId Assert-OfficeLanguageSupported -LanguageId $LanguageId } $arguments = "/configure `"$configurationFilePath`"" Invoke-OfficeDeploymentTool -Path $Path -Arguments $arguments -Operation 'Installation' } function Uninstall-OfficeProduct { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [ProductId] $ProductId, [Parameter()] [string[]] $LanguageId, [Parameter()] [Channel] $Channel = [Channel]::Current ) $configurationFileParameters = @{ ProductId = $ProductId LanguageId = $LanguageId } $configurationFilePath = New-OfficeRemovalConfigurationFile @configurationFileParameters if (-not ([string]::IsNullOrEmpty($LanguageId))) { Write-Verbose -Message "Validating specified LanguageId(s): $($LanguageId -join ', ')" Assert-LanguageInstalled -LanguageId $LanguageId Assert-OfficeLanguageSupported -LanguageId $LanguageId } $arguments = "/configure `"$configurationFilePath`"" Invoke-OfficeDeploymentTool -Path $Path -Arguments $arguments -Operation 'Uninstallation' } <# .SYNOPSIS Executes the Office Deployment Tool with error handling and logging. .DESCRIPTION A wrapper function that executes the Office Deployment Tool setup.exe with the specified arguments. Provides logging of the operation and validates the exit code. Throws a terminating error if the ODT process fails or returns a non-zero exit code. .PARAMETER Path The full path to the Office Deployment Tool setup executable (setup.exe). .PARAMETER Arguments The command-line arguments to pass to the ODT setup executable. .PARAMETER Operation A descriptive name for the operation being performed (e.g., 'Installation', 'Uninstallation'). .INPUTS None This function does not accept pipeline input. .OUTPUTS None This function does not return output. .EXAMPLE Invoke-OfficeDeploymentTool -Path 'C:\ODT\setup.exe' -Arguments '/configure "config.xml"' -Operation 'Installation' Executes the ODT with the specified configuration file for installation. #> function Invoke-OfficeDeploymentTool { [CmdletBinding()] [OutputType()] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [System.String] $Arguments, [Parameter(Mandatory = $true)] [System.String] $Operation ) Write-Verbose -Message "Starting Office $Operation using ODT at: '$Path'" Write-Verbose -Message "ODT Arguments: $Arguments" try { $processInfo = Start-Process -FilePath $Path -ArgumentList $Arguments -Wait -NoNewWindow -PassThru -ErrorAction Stop Write-Verbose -Message "Office $Operation process completed with exit code: $($processInfo.ExitCode)" if ($processInfo.ExitCode -ne 0) { $errorMessage = "Office $Operation failed. ODT setup.exe returned exit code: $($processInfo.ExitCode)" # Common ODT exit codes for better error messages switch ($processInfo.ExitCode) { 30174 { $errorMessage += ' (Another installation is already in progress)' } 30175 { $errorMessage += ' (This product is not supported on this operating system)' } 30180 { $errorMessage += ' (Insufficient system resources)' } 17002 { $errorMessage += ' (Invalid configuration XML)' } 17004 { $errorMessage += ' (Required update channel not available)' } default { $errorMessage += ' (See ODT documentation for exit code details)' } } throw $errorMessage } Write-Verbose -Message "Office $Operation completed successfully." } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( $_.Exception, "OfficeDeploymentToolFailed", [System.Management.Automation.ErrorCategory]::OperationStopped, $Path ) $PSCmdlet.ThrowTerminatingError($errorRecord) } } function TryGetRegistryValue { param ( [Parameter(Mandatory = $true)] [System.String] $Key, [Parameter(Mandatory = $true)] [System.String] $Property ) if (Test-Path -Path $Key) { try { return (Get-ItemProperty -Path $Key | Select-Object -ExpandProperty $Property) } catch { Write-Verbose "Property `"$($Property)`" could not be found." } } else { Write-Verbose 'Registry key does not exist.' } } function Assert-Administrator { $user = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal $user if (-not $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) { throw "The current user is not running with administrative privileges. Please re-run PowerShell as Administrator." } } function Get-LanguageId { [OutputType([System.String[]])] [CmdletBinding()] param ( [Parameter()] [System.String[]] $LanguageId, [Parameter(Mandatory = $true)] [ProductId] $ProductId ) $validLanguages = @() try { $languagePaths = Get-ChildItem -Path $global:OfficeProductReleaseIdsPath -Recurse -ErrorAction Stop } catch { Write-Verbose -Message ( "Failed to access Office ProductReleaseIds registry path: '{0}'" -f $global:OfficeProductReleaseIdsPath ) return @() } $productFilter = { $_.Name -like "*$ProductId*" } $productLanguagePaths = $languagePaths | Where-Object $productFilter if (-not $productLanguagePaths) { Write-Warning -Message "No language paths found for ProductId: '$ProductId'" return @() } if ($null -eq $LanguageId -or $LanguageId.Count -eq 0) { Write-Verbose -Message 'No LanguageId specified, returning all valid languages.' foreach ($languagePath in $productLanguagePaths) { $languageCode = $languagePath.PSChildName if (Test-SupportedLanguageId -LanguageId $languageCode) { $validLanguages += $languageCode } } } else { foreach ($requestedLanguage in $LanguageId) { $matchingLanguagePath = $productLanguagePaths | Where-Object { $_.Name -like "*$requestedLanguage*" } if ($matchingLanguagePath) { Write-Verbose -Message "Valid language found: '$requestedLanguage' for ProductId: '$ProductId' at: '$matchingLanguagePath'" $validLanguages += $requestedLanguage } else { Write-Warning -Message "Language '$requestedLanguage' is not valid for ProductId: '$ProductId'" } } } return $validLanguages } #endregion Functions #region Classes [DSCResource()] class Office365Installer { [DscProperty(Key, Mandatory = $true)] [System.String] $Path [DscProperty()] [ProductId] $ProductId = 'O365ProPlusRetail' [DscProperty()] [PackageId[]] $ExcludeApps = @() [DscProperty()] [Channel] $Channel = [Channel]::Current [DscProperty()] [System.String[]] $LanguageId [DscProperty()] [System.Boolean] $Exist = $true Office365Installer() { } [Office365Installer] Get() { $currentState = [Office365Installer]::new() # TODO: Have to validate if it can contain multiple ProductIds $productReleaseIds = TryGetRegistryValue -Key $global:OfficeProductReleaseIdsPath -Property 'ProductReleaseIds' $currentState.ProductId = ($null -ne $productReleaseIds) ? ([ProductId]($productReleaseIds)) : $this.ProductId $officeInstalled = Get-OfficeInstallation -ProductId $this.ProductId $currentState.ExcludeApps = $officeInstalled.ExcludedApps $currentState.Exist = $officeInstalled.Installed $currentState.Path = $this.Path $currentState.Channel = Get-OfficeGroupPolicyChannelSetting $currentState.LanguageId = Get-LanguageId -LanguageId $this.LanguageId -ProductId $this.ProductId return $currentState } [bool] Test() { $currentState = $this.Get() if ($currentState.Exist -ne $this.Exist) { return $false } if ($currentState.ExcludeApps -ne $this.ExcludeApps) { return $false } # default channel is Current, so if the current state is different, return false if ($currentState.Channel -ne $this.Channel -or $currentState.Channel -ne [Channel]::Current) { return $false } if ($currentState.ProductId -ne $this.ProductId) { return $false } if ($currentState.LanguageId -ne $this.LanguageId) { return $false } return $true } [void] Set() { if ($this.Test()) { return } # before installing, ensure we have admin rights (known issue with ODT) Assert-Administrator # check if the path is actually the ODT setup.exe Assert-OfficeDeploymentToolSetup -Path $this.Path if ($this.Exist) { $this.Install($false) } else { $this.Uninstall($false) } } [void] Install([bool] $preTest) { if ($preTest -and $this.Test()) { return } $installParams = @{ Path = $this.Path ProductId = $this.ProductId Channel = $this.Channel LanguageId = $this.LanguageId ExcludeApps = $this.ExcludeApps } Install-OfficeProduct @installParams } [void] Install() { $this.Install($true) } [void] Uninstall([bool] $preTest) { $uninstallParams = @{ Path = $this.Path ProductId = $this.ProductId Channel = $this.Channel LanguageId = $this.LanguageId } Uninstall-OfficeProduct @uninstallParams } [void] Uninstall() { $this.Uninstall($true) } #endregion Classes } |