Modules/xExchangeHelper.psm1
#Gets the existing Remote PowerShell session to Exchange, if it exists function GetExistingExchangeSession { return (Get-PSSession -Name "DSCExchangeSession" -ErrorAction SilentlyContinue) } #Establishes a Exchange remote powershell session to the local server. Reuses the session if it already exists. function GetRemoteExchangeSession { [CmdletBinding()] param ( [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [System.String[]] $CommandsToLoad, $SetupProcessName = "ExSetup*" ) #Check if Exchange Setup is running. If so, we need to throw an exception, as a running Exchange DSC resource will block Exchange Setup from working properly. if (Get-IsSetupRunning -SetupProcessName $SetupProcessName) { throw "Exchange Setup is currently running. Preventing creation of new Remote PowerShell session to Exchange." } #See if the session already exists $Session = GetExistingExchangeSession #Attempt to reuse the session if we found one if ($null -ne $Session) { if ($Session.State -eq "Opened") { Write-Verbose "Reusing existing Remote Powershell Session to Exchange" } else #Session is in an unexpected state. Remove it so we can rebuild it { RemoveExistingRemoteSession $Session = $null } } #Either the session didn't exist, or it was broken and we nulled it out. Create a new one if ($null -eq $Session) { #First make sure we are on a valid server version, and that Exchange is fully installed if (!(Get-IsSetupComplete -Verbose:$VerbosePreference)) { throw 'A supported version of Exchange is either not present, or not fully installed on this machine.' } Write-Verbose "Creating new Remote Powershell session to Exchange" #Get local server FQDN $machineDomain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain.ToLower() $serverName = $env:computername.ToLower() $serverFQDN = $serverName + "." + $machineDomain #Override chatty banner, because chatty New-Alias Get-ExBanner Out-Null New-Alias Get-Tip Out-Null #Load built in Exchange functions, and create session $exbin = Join-Path -Path ((Get-ItemProperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup).MsiInstallPath) -ChildPath "bin" $remoteExchange = Join-Path -Path "$($exbin)" -ChildPath "RemoteExchange.ps1" . $remoteExchange $Session = _NewExchangeRunspace -fqdn $serverFQDN -credential $Credential -UseWIA $false -AllowRedirection $false #Remove the aliases we created earlier Remove-Item Alias:Get-ExBanner Remove-Item Alias:Get-Tip if ($null -ne $Session) { $Session.Name = "DSCExchangeSession" } } #If the session is still null here, things went wrong. Throw exception if ($null -eq $Session) { throw "Failed to establish remote Powershell session to FQDN: $($serverFQDN)" } else #Import the session globally { #Temporarily set Verbose to SilentlyContinue so the Session and Module import isn't noisy $oldVerbose = $VerbosePreference $VerbosePreference = "SilentlyContinue" if ($CommandsToLoad.Count -gt 0) { $moduleInfo = Import-PSSession $Session -WarningAction SilentlyContinue -DisableNameChecking -AllowClobber -CommandName $CommandsToLoad -Verbose:0 } else { $moduleInfo = Import-PSSession $Session -WarningAction SilentlyContinue -DisableNameChecking -AllowClobber -Verbose:0 } Import-Module $moduleInfo -Global -DisableNameChecking #Set Verbose back $VerbosePreference = $oldVerbose } } #Removes any Remote Sessions that have been setup by us function RemoveExistingRemoteSession { [CmdletBinding()] param() $sessions = GetExistingExchangeSession if ($null -ne $sessions) { Write-Verbose "Removing existing remote Powershell sessions" GetExistingExchangeSession | Remove-PSSession } } #Checks whether a supported version of Exchange is at least partially installed by looking for Exchange's product GUID function Get-IsExchangePresent { $version = Get-ExchangeVersion if ($version -in '2013','2016','2019') { return $true } else { return $false } } #Gets the installed Exchange Version, and returns the number as a string. #Returns N/A if the version cannot be found, and will optionally throw an exception #if ThrowIfUnknownVersion was set to $true. function Get-ExchangeVersion { param ([bool]$ThrowIfUnknownVersion = $false) $version = "N/A" $uninstall20162019Key = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{CD981244-E9B8-405A-9026-6AEB9DCEF1F1}' -ErrorAction SilentlyContinue if ($null -ne $uninstall20162019Key) { if ($uninstall20162019Key.GetValue('VersionMajor') -eq 15 -and $uninstall20162019Key.GetValue('VersionMinor') -eq 2) { $version = '2019' } else { $version = '2016' } } elseif ($null -ne (Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{4934D1EA-BE46-48B1-8847-F1AF20E892C1}' -ErrorAction SilentlyContinue)) { $version = '2013' } elseif ($ThrowIfUnknownVersion) { throw "Failed to discover a known Exchange Version" } return $version } #Checks whether Setup fully completed function Get-IsSetupComplete { [CmdletBinding()] [OutputType([System.Boolean])] param() $exchangePresent = Get-IsExchangePresent $setupPartiallyCompleted = Get-IsSetupPartiallyCompleted -Verbose:$VerbosePreference if ($exchangePresent -eq $true -and $setupPartiallyCompleted -eq $false) { Write-Verbose -Message 'Exchange is present and setup is detected as being fully complete.' $isSetupComplete = $true } else { Write-Verbose -Message "Exchange setup detected as not being fully complete. Exchange Present: $exchangePresent. Setup Partially Complete: $setupPartiallyCompleted." $isSetupComplete = $false } return $isSetupComplete } #Checks whether any Setup watermark keys exist which means that a previous installation of setup had already started but not completed function Get-IsSetupPartiallyCompleted { [CmdletBinding()] param() Write-Verbose -Message 'Checking if setup is partially complete' $isPartiallyCompleted = $false #Now check if setup actually completed successfully [System.String[]]$roleKeys = "CafeRole","ClientAccessRole","FrontendTransportRole","HubTransportRole","MailboxRole","UnifiedMessagingRole" foreach ($key in $roleKeys) { $values = $null $values = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key" -ErrorAction SilentlyContinue if ($null -ne $values) { Write-Verbose -Message "Checking values at key 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key'" if ($null -ne $values.UnpackedVersion) { #If ConfiguredVersion is missing, or Action or Watermark or present, setup needs to be resumed if ($null -eq $values.ConfiguredVersion) { Write-Warning -Message "Registry value missing. Setup considered partially complete. Location: 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key\ConfiguredVersion'." $isPartiallyCompleted = $true } if ($null -ne $values.Action) { Write-Warning -Message "Registry value present. Setup considered partially complete. Location: 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key\Action'. Value: '$($values.Action)'." $isPartiallyCompleted = $true } if ($null -ne $values.Watermark) { Write-Warning -Message "Registry value present. Setup considered partially complete. Location: 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key\Watermark'. Value: '$($values.Watermark)'." $isPartiallyCompleted = $true } } else { Write-Verbose -Message "Value not present 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\$key\UnpackedVersion'. Skipping remaining value checks for key." } } } return $isPartiallyCompleted } #Checks for the exact status of Exchange setup and returns the results in a Hashtable function Get-InstallStatus { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter()] [System.String] $Arguments ) Write-Verbose -Message 'Checking Exchange Install Status' $shouldStartInstall = $false $shouldInstallLanguagePack = Get-ShouldInstallLanguagePack -Arguments $Arguments $setupRunning = Get-IsSetupRunning $setupComplete = Get-IsSetupComplete -Verbose:$VerbosePreference $exchangePresent = Get-IsExchangePresent if ($setupRunning -or $setupComplete) { if($shouldInstallLanguagePack -and $setupComplete) { $shouldStartInstall = $true } else { #Do nothing. Either Install is already running, or it's already finished successfully } } elseif (!$setupComplete) { $shouldStartInstall = $true } Write-Verbose -Message "Finished Checking Exchange Install Status. ShouldInstallLanguagePack: $shouldInstallLanguagePack. SetupRunning: $setupRunning. SetupComplete: $setupComplete. ExchangePresent: $exchangePresent. ShouldStartInstall: $shouldStartInstall." $returnValue = @{ ShouldInstallLanguagePack = $shouldInstallLanguagePack SetupRunning = $setupRunning SetupComplete = $setupComplete ExchangePresent = $exchangePresent ShouldStartInstall = $shouldStartInstall } $returnValue } #Check for missing registry keys that may cause Exchange setup to try to restart WinRM mid setup , which will in turn cause the DSC resource to fail #If any required keys are missing, configure WinRM, then force a reboot function Set-WSManConfigStatus { # Suppressing this rule because $global:DSCMachineStatus is used to trigger a reboot. [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] <# Suppressing this rule because $global:DSCMachineStatus is only set, never used (by design of Desired State Configuration). #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope='Function', Target='DSCMachineStatus')] [CmdletBinding()] [OutputType([System.Boolean])] param () $needReboot = $false $wsmanKey = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN' -ErrorAction SilentlyContinue if ($null -ne $wsmanKey) { if ($null -eq $wsmanKey.UpdatedConfig) { $needReboot = $true Write-Verbose "Value 'UpdatedConfig' missing from registry key HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN. Running: winrm i restore winrm/config" Set-Location "$($env:windir)\System32\inetsrv" winrm i restore winrm/config | Out-Null Write-Verbose -Message 'Machine needs to be rebooted before Exchange setup can proceed' $global:DSCMachineStatus = 1 } } else { throw 'Unable to find registry key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN' } return $needReboot } function Get-ShouldInstallLanguagePack { param ( [Parameter()] [System.String] $Arguments ) if($Arguments -match '(?<=/AddUMLanguagePack:)(([a-z]{2}-[A-Z]{2},?)+)(?=\s)') { $Cultures = $Matches[0] Write-Verbose "AddUMLanguagePack parameters detected: $Cultures" $Cultures = $Cultures -split ',' foreach($Culture in $Cultures) { if((IsUMLanguagePackInstalled -Culture $Culture) -eq $false) { Write-Verbose "UM Language Pack: $Culture is not installed" return $true } } } return $false } #Checks whether setup is running by looking for if the ExSetup.exe process currently exists function Get-IsSetupRunning { param([System.String]$SetupProcessName = "ExSetup*") return ($null -ne (Get-Process -Name $SetupProcessName -ErrorAction SilentlyContinue)) } #Checks if two strings are equal, or are both either null or empty function CompareStrings { param([System.String]$String1, [System.String]$String2, [switch]$IgnoreCase) if (([System.String]::IsNullOrEmpty($String1) -and [System.String]::IsNullOrEmpty($String2))) { return $true } else { if ($IgnoreCase -eq $true) { return ($String1 -like $String2) } else { return ($String1 -clike $String2) } } } #Checks if two bools are equal, or are both either null or false function CompareBools($Bool1, $Bool2) { if($Bool1 -ne $Bool2) { if (!(($null -eq $Bool1 -and $Bool2 -eq $false) -or ($null -eq $Bool2 -and $Bool1 -eq $false))) { return $false } } return $true } #Takes a string which should be in timespan format, and compares it to an actual EnhancedTimeSpan object. Returns true if they are equal function CompareTimespanWithString { param([Microsoft.Exchange.Data.EnhancedTimeSpan]$TimeSpan, [System.String]$String) try { $converted = [Microsoft.Exchange.Data.EnhancedTimeSpan]::Parse($String) return ($TimeSpan.Equals($converted)) } catch { throw "String '$($String)' is not in a valid format for an EnhancedTimeSpan" } return $false } #Takes a string which should be in ByteQuantifiedSize format, and compares it to an actual ByteQuantifiedSize object. Returns true if they are equal function CompareByteQuantifiedSizeWithString { param([Microsoft.Exchange.Data.ByteQuantifiedSize]$ByteQuantifiedSize, [System.String]$String) try { $converted = [Microsoft.Exchange.Data.ByteQuantifiedSize]::Parse($String) return ($ByteQuantifiedSize.Equals($converted)) } catch { throw "String '$($String)' is not in a valid format for a ByteQuantifiedSize" } } #Takes a string which should be in Microsoft.Exchange.Data.Unlimited format, and compares with an actual Unlimited object. Returns true if they are equal. function CompareUnlimitedWithString { param($Unlimited, [System.String]$String) if ($Unlimited.IsUnlimited) { return (CompareStrings -String1 "Unlimited" -String2 $String -IgnoreCase) } elseif ((CompareStrings -String1 "Unlimited" -String2 $String -IgnoreCase) -and !$Unlimited.IsUnlimited) { return $false } elseif (($Unlimited.Value -is [System.Int32]) -and !$Unlimited.IsUnlimited) { return (CompareStrings -String1 $Unlimited -String2 $String -IgnoreCase) } else { return (CompareByteQuantifiedSizeWithString -ByteQuantifiedSize $Unlimited -String $String) } } #Takes an ADObjectId, gets a mailbox from it, and checks if it's EmailAddresses property contains the given string. #The Get-Mailbox cmdlet must be loaded for this function to succeed. function CompareADObjectIdWithEmailAddressString { param([Microsoft.Exchange.Data.Directory.ADObjectId]$ADObjectId, [System.String]$String) if ($null -ne (Get-Command Get-Mailbox -ErrorAction SilentlyContinue)) { $mailbox = $ADObjectId | Get-Mailbox -ErrorAction SilentlyContinue return ($mailbox.EmailAddresses.Contains($String)) } else { Write-Error "CompareADObjectIdWithEmailAddressString requires the Get-Mailbox cmdlert" return $false } } #Takes a string containing a given separator, and breaks it into a string array function StringToArray { param([System.String]$StringIn, [char]$Separator) [System.String[]]$array = $StringIn.Split($Separator) for ($i = 0; $i -lt $array.Length; $i++) { $array[$i] = $array[$i].Trim() } return $array } #Takes an array of strings and converts all elements to lowercase function StringArrayToLower { param([System.String[]]$Array) for ($i = 0; $i -lt $Array.Count; $i++) { if (!([System.String]::IsNullOrEmpty($Array[$i]))) { $Array[$i] = $Array[$i].ToLower() } } return $Array } #Checks whether two arrays have the same contents, where element order doesn't matter function Compare-ArrayContent { param([System.String[]]$Array1, [System.String[]]$Array2, [switch]$IgnoreCase) $hasSameContents = $true if ($Array1.Length -ne $Array2.Length) { $hasSameContents = $false } elseif ($Array1.Count -gt 0 -and $Array2.Count -gt 0) { if ($IgnoreCase -eq $true) { $Array1 = StringArrayToLower -Array $Array1 $Array2 = StringArrayToLower -Array $Array2 } foreach ($str in $Array1) { if (!($Array2.Contains($str))) { $hasSameContents = $false break } } } return $hasSameContents } #Checks whether Array2 contains all elements of Array1 (Array2 may be larger than Array1) function Array2ContainsArray1Contents { param([System.String[]]$Array1, [System.String[]]$Array2, [switch]$IgnoreCase) $hasContents = $true if ($Array1.Length -eq 0) #Do nothing, as Array2 at a minimum contains nothing {} elseif ($Array2.Length -eq 0) #Array2 is empty and Array1 is not. Return false { $hasContents = $false } else { if ($IgnoreCase -eq $true) { $Array1 = StringArrayToLower -Array $Array1 $Array2 = StringArrayToLower -Array $Array2 } foreach ($str in $Array1) { if (!($Array2.Contains($str))) { $hasContents = $false break } } } return $hasContents } #Takes $PSBoundParameters from another function and adds in the keys and values from the given Hashtable function AddParameters { param($PSBoundParametersIn, [Hashtable]$ParamsToAdd) foreach ($key in $ParamsToAdd.Keys) { if (!($PSBoundParametersIn.ContainsKey($key))) #Key doesn't exist, so add it with value { $PSBoundParametersIn.Add($key, $ParamsToAdd[$key]) | Out-Null } else #Key already exists, so just replace the value { $PSBoundParametersIn[$key] = $ParamsToAdd[$key] } } } #Takes $PSBoundParameters from another function. If ParamsToRemove is specified, it will remove each param. #If ParamsToKeep is specified, everything but those params will be removed. If both ParamsToRemove and ParamsToKeep #are specified, only ParamsToKeep will be used. function RemoveParameters { param($PSBoundParametersIn, [System.String[]]$ParamsToKeep, [System.String[]]$ParamsToRemove) if ($ParamsToKeep.Count -gt 0) { [System.String[]]$ParamsToRemove = @() $lowerParamsToKeep = StringArrayToLower -Array $ParamsToKeep foreach ($key in $PSBoundParametersIn.Keys) { if (!($lowerParamsToKeep.Contains($key.ToLower()))) { $ParamsToRemove += $key } } } if ($ParamsToRemove.Count -gt 0) { foreach ($param in $ParamsToRemove) { $PSBoundParametersIn.Remove($param) | Out-Null } } } <# .SYNOPSIS Inspects the input $PSBoundParametersIn hashtable, and removes any parameters that do not work with the version of Exchange on this server. .PARAMETER PSBoundParametersIn The $PSBoundParameters hashtable from the calling function. .PARAMETER ParamName The parameter to check for and remove if not applicable to this server version. .PARAMETER ParamExistsInVersion The parameter to check for and remove if not applicable to this server version. #> function Remove-NotApplicableParamsForVersion { [CmdletBinding()] param ( [System.Collections.Hashtable] $PSBoundParametersIn, [System.String] $ParamName, [System.String] $ResourceName, [ValidateSet('2013','2016','2019')] [System.String[]] $ParamExistsInVersion ) if ($PSBoundParametersIn.ContainsKey($ParamName)) { $serverVersion = Get-ExchangeVersion if ($serverVersion -notin $ParamExistsInVersion) { Write-Warning "$($ParamName) is not a valid parameter for $($ResourceName) in Exchange $($serverVersion). Skipping usage." RemoveParameters -PSBoundParametersIn $PSBoundParametersIn -ParamsToRemove $ParamName } } } function SetEmptyStringParamsToNull { param($PSBoundParametersIn) [System.String[]] $emptyStringKeys = @() #First find all parameters that are a string, and are an empty string ("") foreach ($key in $PSBoundParametersIn.Keys) { if ($null -ne $PSBoundParametersIn[$key] -and $PSBoundParametersIn[$key].GetType().Name -eq "String" -and $PSBoundParametersIn[$key] -eq "") { $emptyStringKeys += $key } } #Now that we have the keys, set their values to null foreach ($key in $emptyStringKeys) { $PSBoundParametersIn[$key] = $null } } function VerifySetting { [CmdletBinding()] [OutputType([System.Boolean])] param([System.String]$Name, [System.String]$Type, $ExpectedValue, $ActualValue, $PSBoundParametersIn) $returnValue = $true if ($PSBoundParametersIn.ContainsKey($Name)) { if ($Type -like "String") { if ((CompareStrings -String1 $ExpectedValue -String2 $ActualValue -IgnoreCase) -eq $false) { $returnValue = $false } } elseif ($Type -like "Boolean") { if ((CompareBools -Bool1 $ExpectedValue -Bool2 $ActualValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "Array") { if ((Compare-ArrayContent -Array1 $ExpectedValue -Array2 $ActualValue -IgnoreCase) -eq $false) { $returnValue = $false } } elseif ($Type -like "Int") { if ($ExpectedValue -ne $ActualValue) { $returnValue = $false } } elseif ($Type -like "Unlimited") { if ((CompareUnlimitedWithString -Unlimited $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "Timespan") { if ((CompareTimespanWithString -TimeSpan $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "ADObjectID") { if ((CompareADObjectIdWithEmailAddressString -ADObjectId $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "ByteQuantifiedSize") { if ((CompareByteQuantifiedSizeWithString -ByteQuantifiedSize $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "IPAddress") { if ((CompareIPAddresseWithString -IPAddress $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "IPAddresses") { if ((CompareIPAddressesWithArray -IPAddresses $ActualValue -Array $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "SMTPAddress") { if ((CompareSmtpAdressWithString -SmtpAddress $ActualValue -String $ExpectedValue) -eq $false) { $returnValue = $false } } elseif ($Type -like "PSCredential") { if ((Compare-PSCredential -Cred1 $ActualValue -Cred2 $ExpectedValue ) -eq $false) { $returnValue = $false } } elseif ($Type -like "ExtendedProtection") { if ((StringArrayToLower $ExpectedValue).Contains('none')) { if (-not [System.String]::IsNullOrEmpty($ActualValue)) { $returnValue = $false } } else { if ((Compare-ArrayContent -Array1 $ExpectedValue -Array2 $ActualValue -IgnoreCase) -eq $false) { $returnValue = $false } } } else { throw "Type not found: $($Type)" } } if ($returnValue -eq $false) { ReportBadSetting -SettingName $Name -ExpectedValue $ExpectedValue -ActualValue $ActualValue -Verbose:$VerbosePreference } return $returnValue } function ReportBadSetting { param($SettingName, $ExpectedValue, $ActualValue) Write-Verbose "Invalid setting '$($SettingName)'. Expected value: '$($ExpectedValue)'. Actual value: '$($ActualValue)'" } function LogFunctionEntry { [CmdletBinding()] param([Hashtable]$Parameters) $callingFunction = (Get-PSCallStack)[1].FunctionName if ($Parameters.Count -gt 0) { $parametersString = "" foreach ($key in $Parameters.Keys) { $value = $Parameters[$key] if ($parametersString -ne "") { $parametersString += ", " } $parametersString += "$($key) = '$($value)'" } Write-Verbose "Entering function '$($callingFunction)'. Notable parameters: $($parametersString)" } else { Write-Verbose "Entering function '$($callingFunction)'." } } function StartScheduledTask { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String] $Path, [System.String] $Arguments, [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [System.String] $TaskName, [System.String] $WorkingDirectory, [System.UInt32] $MaxWaitMinutes = 0, [System.UInt32] $TaskPriority = 4 ) $tName = "$([guid]::NewGuid().ToString())" if ($PSBoundParameters.ContainsKey("TaskName")) { $tName = "$($TaskName) $($tName)" } $action = New-ScheduledTaskAction -Execute "$($Path)" -Argument "$($Arguments)" if ($PSBoundParameters.ContainsKey("WorkingDirectory")) { $action.WorkingDirectory = $WorkingDirectory } Write-Verbose "Created Scheduled Task with name: $($tName)" Write-Verbose "Task Action: $($Path) $($Arguments)" #Use 'NT AUTHORITY\SYSTEM' as the run as account unless a specific Credential was provided $credParams = @{User = "NT AUTHORITY\SYSTEM"} if ($PSBoundParameters.ContainsKey("Credential")) { $credParams["User"] = $Credential.UserName $credParams.Add("Password", $Credential.GetNetworkCredential().Password) } $task = Register-ScheduledTask @credParams -TaskName "$($tName)" -Action $action -RunLevel Highest -ErrorVariable errRegister -ErrorAction SilentlyContinue if (0 -lt $errRegister.Count) { throw $errRegister[0] } elseif ($null -ne $task -and $task.State -eq "Ready") { #Set a time limit on the task $taskSettings = $task.Settings $taskSettings.ExecutionTimeLimit = "PT$($MaxWaitMinutes)M" $taskSettings.Priority = $TaskPriority Set-ScheduledTask @credParams -TaskName "$($task.TaskName)" -Settings $taskSettings Write-Verbose "Starting task at: $([DateTime]::Now)" $task | Start-ScheduledTask } else { throw "Failed to register Scheduled Task" } } function CheckForCmdletParameter { [CmdletBinding()] [OutputType([System.Boolean])] param([System.String]$CmdletName, [System.String]$ParameterName) [bool]$hasParameter = $false $command = Get-Command -Name "$($CmdletName)" -ErrorAction SilentlyContinue if ($null -ne $command -and $null -ne $command.Parameters) { if ($command.Parameters.ContainsKey($ParameterName)) { $hasParameter = $true } } return $hasParameter } <# .SYNOPSIS Returns the most recent error in the $Global:Error Variable #> function Get-PreviousError { # Suppressing this rule to allow use of the built-in Global:Error variable [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] [OutputType([System.Management.Automation.ErrorRecord])] param() $previousError = $null if ($Global:Error.Count -gt 0) { $previousError = $Global:Error[0] } return $previousError } <# .SYNOPSIS Compares the most recent error in the $Global:Error variable to the input $PreviousError variable. If they are not the same, throws an exception. .PARAMETER CmdletBeingRun The name of the cmdlet that was run immediately prior to calling this function. .PARAMETER PreviousError The previous known error variable to compare against the most recent error that has occurred. #> function Assert-NoNewError { # Suppressing this rule to allow use of the built-in Global:Error variable [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')] [CmdletBinding()] param ( [System.String] $CmdletBeingRun, [System.Management.Automation.ErrorRecord] $PreviousError ) #Throw an exception if errors were encountered if ($Global:Error.Count -gt 0 -and $PreviousError -ne $Global:Error[0]) { throw "Failed to run $CmdletBeingRun with: $($Global:Error[0])" } } function RestartAppPoolIfExists { [CmdletBinding()] param([System.String]$Name) $state = Get-WebAppPoolState -Name $Name -ErrorAction SilentlyContinue if ($null -ne $state) { Restart-WebAppPool -Name $Name } else { Write-Verbose "Application pool with name '$($Name)' does not exist. Skipping application pool restart." } } #Checks if the UM language pack for the specified culture is installed function IsUMLanguagePackInstalled { Param ( [ValidateNotNullOrEmpty()] [System.String] $Culture ) return [bool](Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\UnifiedMessagingRole\LanguagePacks').$Culture } #Compares a single IPAddress with a string function CompareIPAddresseWithString { [CmdletBinding()] [OutputType([System.Boolean])] param([System.Net.IPAddress]$IPAddress, [System.String]$String) if (($null -eq $IPAddress -and !([System.String]::IsNullOrEmpty($String))) -or ($null -ne $IPAddress -and [System.String]::IsNullOrEmpty($String))) { $returnValue = $false } elseif ($null -eq $IPAddress -and [System.String]::IsNullOrEmpty($String)) { $returnValue = $true } else { $returnValue =($IPAddress.Equals([System.Net.IPAddress]::Parse($string))) } if ($returnValue -eq $false) { ReportBadSetting -SettingName $IPAddress -ExpectedValue $ExpectedValue -ActualValue $IPAddress -Verbose:$VerbosePreference } return $returnValue } #Compares a SMTP address with a string function CompareSmtpAdressWithString { [CmdletBinding()] [OutputType([System.Boolean])] param($SmtpAddress,[System.String]$String) if (($null -eq $SmtpAddress) -and ([System.String]::IsNullOrEmpty($String))) { Write-Verbose "Expected and actual value is empty, therefore equal!" return $true } elseif (($null -eq $SmtpAddress) -and -not ([System.String]::IsNullOrEmpty($String))) { return $false } elseif ($SmtpAddress.Gettype() -eq [Microsoft.Exchange.Data.SmtpAddress]) { if ([System.String]::IsNullOrEmpty($String)) { return $false } else { return($SmtpAddress.Equals([Microsoft.Exchange.Data.SmtpAddress]::Parse($string))) } } else { Write-Verbose "No type of [Microsoft.Exchange.Data.SmtpAddress]!" return $false } } #Compares IPAddresses with an array function CompareIPAddressesWithArray { [CmdletBinding()] [OutputType([System.Boolean])] param($IPAddresses, [Array]$Array) if (([System.String]::IsNullOrEmpty($IPAddresses)) -and ([System.String]::IsNullOrEmpty($Array))) { $returnValue = $true } elseif ((([System.String]::IsNullOrEmpty($IPAddresses)) -and !(([System.String]::IsNullOrEmpty($Array)))) -or (!(([System.String]::IsNullOrEmpty($IPAddresses))) -and ([System.String]::IsNullOrEmpty($Array)))) { $returnValue = $false } else { Compare-ArrayContent -Array1 $IPAddresses -Array2 $Array } if ($returnValue -eq $false) { ReportBadSetting -SettingName $IPAddresses -ExpectedValue $ExpectedValue -ActualValue $IPAddress -Verbose:$VerbosePreference } return $returnValue } #Compares two give PSCredential function Compare-PSCredential { [CmdletBinding()] [OutputType([System.Boolean])] param ( #[System.Management.Automation.PSCredential] #[System.Management.Automation.Credential()] $Cred1, #[System.Management.Automation.PSCredential] #[System.Management.Automation.Credential()] $Cred2 ) Begin { $returnValue = $false if ($null -ne $Cred1) { $Cred1User = $Cred1.UserName $Cred1Password = $Cred1.GetNetworkCredential().Password } if ($null -ne $Cred2) { $Cred2User = $Cred2.UserName $Cred2Password = $Cred2.GetNetworkCredential().Password } } Process { if (($Cred1User -ceq $Cred2User) -and ($Cred1Password -ceq $Cred2Password)){ Write-Verbose "Credentials match" $returnValue = $true } else{ Write-Verbose "Credentials don't match" Write-Verbose "Cred1:$($Cred1User) Cred2:$($Cred2User)" Write-Verbose "Cred1:$($Cred1Password) Cred2:$($Cred2Password)" } } End { return $returnValue } } #helper function to check SPN for Dotless name function Test-ExtendedProtectionSPNList { [CmdletBinding()] [OutputType([System.Boolean])] Param ( [System.String[]]$SPNList, [System.String[]]$Flags ) Begin { #initialize variable [System.Boolean]$IsDotless = $false [System.Boolean]$returnValue = $true [System.Boolean]$InvalidFlags = $false #check for invalid ExtendedProtectionFlags if (-not [System.String]::IsNullOrEmpty($Flags)) { if ((StringArrayToLower $Flags).Contains("none") -and ($Flags.Count -gt 1)) { Write-Verbose "Invalid combination of ExtendedProtectionFlags detected!" $InvalidFlags = $true $returnValue = $false } } #check for invalid formatted and Dotless SPNs if ((-not [System.String]::IsNullOrEmpty($SPNList)) -and (-not $InvalidFlags)) { #check for Dotless SPN foreach ($S in $SPNList) { $Name = $S.Split('/')[1] if ([System.String]::IsNullOrEmpty($Name)) { Write-Verbose "Invalid SPN:$($S)" break } else { if (-not $Name.Contains('.')) { Write-Verbose -Message "Found Dotless SPN:$($Name)" $IsDotless = $true break } } } } } Process { #check if AllowDotless is set in Flags if($IsDotless) { if([System.String]::IsNullOrEmpty($Flags)) { Write-Verbose "AllowDotless SPN found, but Flags is NULL!" $returnValue = $false } else { if( -not (StringArrayToLower $Flags).Contains("allowdotlessspn")) { Write-Verbose "AllowDotless is not found in Flags!" $returnValue = $false } } } } End { $returnValue } } <# .SYNOPSIS Checks if the current Exchange Server version is contained within the $SupportedVersions parameter. If it is not, throws an exception. .PARAMETER ObjectOrOperationName The name of object type or operation name that is about to be utilized if the call to this function does not throw an exception. .PARAMETER SupportedVersions The allowed Exchange Server versions that the object or operation is allowed to be utilized on. #> function Assert-IsSupportedWithExchangeVersion { [CmdletBinding()] param ( [System.String] $ObjectOrOperationName, [System.String[]] $SupportedVersions ) $serverVersion = Get-ExchangeVersion if ($serverVersion -notin $SupportedVersions) { throw "$ObjectOrOperationName is not supported in Exchange Server $serverVersion" } } Export-ModuleMember -Function * |