ExchangeEssentials.psm1
function Convert-ExchangeEmail { <# .SYNOPSIS Function that helps converting Exchange email address list into readable, exportable format. .DESCRIPTION Function that helps converting Exchange email address list into readable, exportable format. .PARAMETER Emails List of emails as available in Exchange or Exchange Online, otherwise known as proxy addresses list .PARAMETER Separator .PARAMETER RemoveDuplicates .PARAMETER RemovePrefix .PARAMETER AddSeparator .EXAMPLE $Emails = @() $Emails += 'SIP:test@email.com' $Emails += 'SMTP:elo@maiu.com' $Emails += 'sip:elo@maiu.com' $Emails += 'Spo:dfte@sdsd.com' $Emails += 'SPO:myothertest@sco.com' Convert-ExchangeEmail -Emails $Emails -RemovePrefix -RemoveDuplicates -AddSeparator .NOTES General notes #> [CmdletBinding()] param( [string[]] $Emails, [string] $Separator = ', ', [switch] $RemoveDuplicates, [switch] $RemovePrefix, [switch] $AddSeparator ) if ($RemovePrefix) { $Emails = $Emails -replace 'smtp:', '' -replace 'sip:', '' -replace 'spo:', '' } if ($RemoveDuplicates) { $Emails = $Emails | Sort-Object -Unique } if ($AddSeparator) { $Emails = $Emails -join $Separator } return $Emails } function Convert-ExchangeSize { [cmdletbinding()] param( [validateset("Bytes", "KB", "MB", "GB", "TB")][string]$To = 'MB', [string]$Size, [int]$Precision = 4, [switch]$Display, [string]$Default = 'N/A' ) if ([string]::IsNullOrWhiteSpace($Size)) { return $Default } $Pattern = [Regex]::new('(?<=\()([0-9]*[,.].*[0-9])') $Value = ($Size | Select-String $Pattern -AllMatches).Matches.Value if ($null -ne $Value) { $Value = $Value.Replace(',', '').Replace('.', '') } switch ($To) { "Bytes" { return $value } "KB" { $Value = $Value / 1KB } "MB" { $Value = $Value / 1MB } "GB" { $Value = $Value / 1GB } "TB" { $Value = $Value / 1TB } } if ($Display) { return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To" } else { return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero) } } function ConvertFrom-DistinguishedName { <# .SYNOPSIS Converts a Distinguished Name to CN, OU, Multiple OUs or DC .DESCRIPTION Converts a Distinguished Name to CN, OU, Multiple OUs or DC .PARAMETER DistinguishedName Distinguished Name to convert .PARAMETER ToOrganizationalUnit Converts DistinguishedName to Organizational Unit .PARAMETER ToDC Converts DistinguishedName to DC .PARAMETER ToDomainCN Converts DistinguishedName to Domain Canonical Name (CN) .PARAMETER ToCanonicalName Converts DistinguishedName to Canonical Name .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName Output: Przemyslaw Klys .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit Output: OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $Con = @( 'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz' 'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz' 'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz' 'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl' 'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz' ) ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName Output: Windows Authorization Access Group Mmm e6d5fd00-385d-4e65-b02d-9da3493ed850 Domain Controllers Microsoft Exchange Security Groups .EXAMPLEE ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName Output: ad.evotec.xyz ad.evotec.xyz\Production\Users ad.evotec.xyz\Production\Users\test .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'ToOrganizationalUnit')] [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')] [Parameter(ParameterSetName = 'ToDC')] [Parameter(ParameterSetName = 'ToDomainCN')] [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'ToLastName')] [Parameter(ParameterSetName = 'ToCanonicalName')] [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent, [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC, [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN, [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName, [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName ) Process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" if ($CN) { $CN } } elseif ($ToOrganizationalUnit) { $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value if ($Value) { $Value } } elseif ($ToMultipleOrganizationalUnit) { if ($IncludeParent) { $Distinguished } while ($true) { $Distinguished = $Distinguished -replace '^.+?,(?=..=)' if ($Distinguished -match '^DC=') { break } $Distinguished } } elseif ($ToDC) { $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' if ($Value) { $Value } } elseif ($ToLastName) { $NewDN = $Distinguished -split ",DC=" if ($NewDN[0].Contains(",OU=")) { [Array] $ChangedDN = $NewDN[0] -split ",OU=" } elseif ($NewDN[0].Contains(",CN=")) { [Array] $ChangedDN = $NewDN[0] -split ",CN=" } else { [Array] $ChangedDN = $NewDN[0] } if ($ChangedDN[0].StartsWith('CN=')) { $ChangedDN[0] -replace 'CN=', '' } else { $ChangedDN[0] -replace 'OU=', '' } } elseif ($ToCanonicalName) { $Domain = $null $Rest = $null foreach ($O in $Distinguished -split '(?<!\\),') { if ($O -match '^DC=') { $Domain += $O.Substring(3) + '.' } else { $Rest = $O.Substring(3) + '\' + $Rest } } if ($Domain -and $Rest) { $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',') } elseif ($Domain) { $Domain.Trim('.') } elseif ($Rest) { $Rest.TrimEnd('\') -replace '\\,', ',' } } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Found = $Distinguished -match $Regex if ($Found) { $Matches.cn } } } } } function Copy-Dictionary { <# .SYNOPSIS Copies dictionary/hashtable .DESCRIPTION Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4 .PARAMETER Dictionary Dictionary to copy .EXAMPLE $Test = [ordered] @{ Test = 'Test' Test1 = @{ Test2 = 'Test2' Test3 = @{ Test4 = 'Test4' } } Test2 = @( "1", "2", "3" ) Test3 = [PSCustomObject] @{ Test4 = 'Test4' Test5 = 'Test5' } } $New1 = Copy-Dictionary -Dictionary $Test $New1 .NOTES #> [alias('Copy-Hashtable', 'Copy-OrderedHashtable')] [cmdletbinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue) return [System.Management.Automation.PSSerializer]::Deserialize($clone) } function Get-FileName { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Extension Parameter description .PARAMETER Temporary Parameter description .PARAMETER TemporaryFileOnly Parameter description .EXAMPLE Get-FileName -Temporary Output: 3ymsxvav.tmp .EXAMPLE Get-FileName -Temporary Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp .EXAMPLE Get-FileName -Temporary -Extension 'xlsx' Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx .NOTES General notes #> [CmdletBinding()] param( [string] $Extension = 'tmp', [switch] $Temporary, [switch] $TemporaryFileOnly ) if ($Temporary) { return [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension") } if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" } } function Get-GitHubVersion { <# .SYNOPSIS Get the latest version of a GitHub repository and compare with local version .DESCRIPTION Get the latest version of a GitHub repository and compare with local version .PARAMETER Cmdlet Cmdlet to find module for .PARAMETER RepositoryOwner Repository owner .PARAMETER RepositoryName Repository name .EXAMPLE Get-GitHubVersion -Cmdlet 'Start-DelegationModel' -RepositoryOwner 'evotecit' -RepositoryName 'DelegationModel' .NOTES General notes #> [cmdletBinding()] param( [Parameter(Mandatory)][string] $Cmdlet, [Parameter(Mandatory)][string] $RepositoryOwner, [Parameter(Mandatory)][string] $RepositoryName ) $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue if ($App) { [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false) $LatestVersion = $GitHubReleases[0] if (-not $LatestVersion.Errors) { if ($App.Version -eq $LatestVersion.Version) { "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($App.Version -lt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($App.Version -gt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { "Current: $($App.Version)" } } else { "Current: Unknown" } } function Get-WinADForestDetails { [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [string] $Filter = '*', [switch] $TestAvailability, [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All', [int[]] $Ports = 135, [int] $PortsTimeout = 100, [int] $PingCount = 1, [switch] $PreferWritable, [switch] $Extended, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } if (-not $ExtendedForestInformation) { $Findings = [ordered] @{ } try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)" return } if (-not $ForestInformation) { return } $Findings['Forest'] = $ForestInformation $Findings['ForestDomainControllers'] = @() $Findings['QueryServers'] = @{ } $Findings['DomainDomainControllers'] = @{ } [Array] $Findings['Domains'] = foreach ($Domain in $ForestInformation.Domains) { if ($IncludeDomains) { if ($Domain -in $IncludeDomains) { $Domain.ToLower() } continue } if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() } } [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop -Writable:$PreferWritable.IsPresent $OrderedDC = [ordered] @{ Domain = $DC.Domain Forest = $DC.Forest HostName = [Array] $DC.HostName IPv4Address = $DC.IPv4Address IPv6Address = $DC.IPv6Address Name = $DC.Name Site = $DC.Site } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)" continue } if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC } $Findings['QueryServers']["$Domain"] = $OrderedDC $Domain } [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) { if ($Domain -notin $DomainsActive) { Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping." continue } $Domain } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0] [Array] $AllDC = try { try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch { Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)" continue } foreach ($S in $DomainControllers) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $Server = [ordered] @{ Domain = $Domain HostName = $S.HostName Name = $S.Name Forest = $ForestInformation.RootDomain Site = $S.Site IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly IsSchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') IsDomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') IsPDC = ($S.OperationMasterRoles -contains 'PDCEmulator') IsRIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') IsInfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') OperatingSystem = $S.OperatingSystem OperatingSystemVersion = $S.OperatingSystemVersion OperatingSystemLong = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion LdapPort = $S.LdapPort SslPort = $S.SslPort DistinguishedName = $S.ComputerObjectDN Pingable = $null WinRM = $null PortOpen = $null Comment = '' } if ($TestAvailability) { if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount } if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status } if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{ Domain = $Domain HostName = '' Name = '' Forest = $ForestInformation.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' DistinguishedName = '' Pingable = $null WinRM = $null PortOpen = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } if ($Extended) { $Findings['DomainsExtended'] = @{ } $Findings['DomainsExtendedNetBIOS'] = @{ } foreach ($DomainEx in $Findings['Domains']) { try { $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{ AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ } ChildDomains = $_.ChildDomains | ForEach-Object -Process { $_ } ComputersContainer = $_.ComputersContainer DeletedObjectsContainer = $_.DeletedObjectsContainer DistinguishedName = $_.DistinguishedName DNSRoot = $_.DNSRoot DomainControllersContainer = $_.DomainControllersContainer DomainMode = $_.DomainMode DomainSID = $_.DomainSID.Value ForeignSecurityPrincipalsContainer = $_.ForeignSecurityPrincipalsContainer Forest = $_.Forest InfrastructureMaster = $_.InfrastructureMaster LastLogonReplicationInterval = $_.LastLogonReplicationInterval LinkedGroupPolicyObjects = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ } LostAndFoundContainer = $_.LostAndFoundContainer ManagedBy = $_.ManagedBy Name = $_.Name NetBIOSName = $_.NetBIOSName ObjectClass = $_.ObjectClass ObjectGUID = $_.ObjectGUID ParentDomain = $_.ParentDomain PDCEmulator = $_.PDCEmulator PublicKeyRequiredPasswordRolling = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ } QuotasContainer = $_.QuotasContainer ReadOnlyReplicaDirectoryServers = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ } ReplicaDirectoryServers = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ } RIDMaster = $_.RIDMaster SubordinateReferences = $_.SubordinateReferences | ForEach-Object -Process { $_ } SystemsContainer = $_.SystemsContainer UsersContainer = $_.UsersContainer } } $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName'] $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx] } catch { Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)" continue } } } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } $Findings } else { $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } } foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainsExtended.Remove($_) $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName' if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) } } } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $S } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } $Findings } } function Start-TimeLog { [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time, [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner', [switch] $Continue ) Begin { } Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } } End { if (-not $Continue) { $Time.Stop() } return $TimeToExecute } } function Write-Color { <# .SYNOPSIS Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. .DESCRIPTION Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. - Ability to use aliases for parameters .PARAMETER Text Text to display on screen and write to log file if specified. Accepts an array of strings. .PARAMETER Color Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER BackGroundColor Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER StartTab Number of tabs to add before text. Default is 0. .PARAMETER LinesBefore Number of empty lines before text. Default is 0. .PARAMETER LinesAfter Number of empty lines after text. Default is 0. .PARAMETER StartSpaces Number of spaces to add before text. Default is 0. .PARAMETER LogFile Path to log file. If not specified no log file will be created. .PARAMETER DateTimeFormat Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss .PARAMETER LogTime If set to $true it will add time to log file. Default is $true. .PARAMETER LogRetry Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2. .PARAMETER Encoding Encoding of the log file. Default is Unicode. .PARAMETER ShowTime Switch to add time to console output. Default is not set. .PARAMETER NoNewLine Switch to not add new line at the end of the output. Default is not set. .PARAMETER NoConsoleOutput Switch to not output to console. Default all output goes to console. .EXAMPLE Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 .EXAMPLE Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 .EXAMPLE Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" .EXAMPLE Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow Write-Color -t "my text" -c yellow -b green Write-Color -text "my text" -c red .EXAMPLE Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput .NOTES Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) #> [alias('Write-Colour')] [CmdletBinding()] param ( [alias ('T')] [String[]]$Text, [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, [alias ('Indent')][int] $StartTab = 0, [int] $LinesBefore = 0, [int] $LinesAfter = 0, [int] $StartSpaces = 0, [alias ('L')] [string] $LogFile = '', [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', [alias ('LogTimeStamp')][bool] $LogTime = $true, [int] $LogRetry = 2, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine, [alias('HideConsole')][switch] $NoConsoleOutput ) if (-not $NoConsoleOutput) { $DefaultColor = $Color[0] if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated." return } if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line before if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } # Add TABS before text if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } # Add SPACES before text if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } # Add Time before output if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { # the real deal coloring if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline } } else { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline } } } } if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host } # Support for no new line if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } # Add empty line after } if ($Text.Count -and $LogFile) { # Save to file $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } $Saved = $false $Retry = 0 Do { $Retry++ try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } $Saved = $true } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))" } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } } } Until ($Saved -eq $true -or $Retry -ge $LogRetry) } } function ConvertTo-OperatingSystem { <# .SYNOPSIS Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .DESCRIPTION Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .PARAMETER OperatingSystem Operating System as returned by Active Directory .PARAMETER OperatingSystemVersion Operating System Version as returned by Active Directory .EXAMPLE $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object { $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force $_ } $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table .EXAMPLE $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber .NOTES General notes #> [CmdletBinding()] param( [string] $OperatingSystem, [string] $OperatingSystemVersion ) if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') { $Systems = @{ '10.0 (22621)' = 'Windows 11 22H2' '10.0 (22000)' = 'Windows 11 21H2' '10.0 (19045)' = 'Windows 10 22H2' '10.0 (19044)' = 'Windows 10 21H2' '10.0 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '10.0 (19041)' = 'Windows 10 2004' '10.0 (18898)' = 'Windows 10 Insider Preview' '10.0 (18363)' = "Windows 10 1909" '10.0 (18362)' = "Windows 10 1903" '10.0 (17763)' = "Windows 10 1809" '10.0 (17134)' = "Windows 10 1803" '10.0 (16299)' = "Windows 10 1709" '10.0 (15063)' = "Windows 10 1703" '10.0 (14393)' = "Windows 10 1607" '10.0 (10586)' = "Windows 10 1511" '10.0 (10240)' = "Windows 10 1507" '10.0.22621' = 'Windows 11 22H2' '10.0.22000' = 'Windows 11 21H2' '10.0.19045' = 'Windows 10 22H2' '10.0.19044' = 'Windows 10 21H2' '10.0.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '10.0.19041' = 'Windows 10 2004' '10.0.18898' = 'Windows 10 Insider Preview' '10.0.18363' = "Windows 10 1909" '10.0.18362' = "Windows 10 1903" '10.0.17763' = "Windows 10 1809" '10.0.17134' = "Windows 10 1803" '10.0.16299' = "Windows 10 1709" '10.0.15063' = "Windows 10 1703" '10.0.14393' = "Windows 10 1607" '10.0.10586' = "Windows 10 1511" '10.0.10240' = "Windows 10 1507" '22621' = 'Windows 11 22H2' '22000' = 'Windows 11 21H2' '19045' = 'Windows 10 22H2' '19044' = 'Windows 10 21H2' '19043' = 'Windows 10 21H1' '19042' = 'Windows 10 20H2' '19041' = 'Windows 10 2004' '18898' = 'Windows 10 Insider Preview' '18363' = "Windows 10 1909" '18362' = "Windows 10 1903" '17763' = "Windows 10 1809" '17134' = "Windows 10 1803" '16299' = "Windows 10 1709" '15063' = "Windows 10 1703" '14393' = "Windows 10 1607" '10586' = "Windows 10 1511" '10240' = "Windows 10 1507" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } elseif ($OperatingSystem -like 'Windows Server*') { $Systems = @{ '10.0 (20348)' = 'Windows Server 2022' '10.0 (19042)' = 'Windows Server 2019 20H2' '10.0 (19041)' = 'Windows Server 2019 2004' '10.0 (18363)' = 'Windows Server 2019 1909' '10.0 (18362)' = "Windows Server 2019 1903" '10.0 (17763)' = "Windows Server 2019 1809" '10.0 (17134)' = "Windows Server 2016 1803" '10.0 (14393)' = "Windows Server 2016 1607" '6.3 (9600)' = 'Windows Server 2012 R2' '6.1 (7601)' = 'Windows Server 2008 R2' '5.2 (3790)' = 'Windows Server 2003' '10.0.20348' = 'Windows Server 2022' '10.0.19042' = 'Windows Server 2019 20H2' '10.0.19041' = 'Windows Server 2019 2004' '10.0.18363' = 'Windows Server 2019 1909' '10.0.18362' = "Windows Server 2019 1903" '10.0.17763' = "Windows Server 2019 1809" '10.0.17134' = "Windows Server 2016 1803" '10.0.14393' = "Windows Server 2016 1607" '6.3.9600' = 'Windows Server 2012 R2' '6.1.7601' = 'Windows Server 2008 R2' '5.2.3790' = 'Windows Server 2003' '20348' = 'Windows Server 2022' '19042' = 'Windows Server 2019 20H2' '19041' = 'Windows Server 2019 2004' '18363' = 'Windows Server 2019 1909' '18362' = "Windows Server 2019 1903" '17763' = "Windows Server 2019 1809" '17134' = "Windows Server 2016 1803" '14393' = "Windows Server 2016 1607" '9600' = 'Windows Server 2012 R2' '7601' = 'Windows Server 2008 R2' '3790' = 'Windows Server 2003' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function Copy-DictionaryManual { [CmdletBinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [ordered] @{} foreach ($Key in $Dictionary.Keys) { $value = $Dictionary.$Key $clonedValue = switch ($Dictionary.$Key) { { $null -eq $_ } { $null continue } { $_ -is [System.Collections.IDictionary] } { Copy-DictionaryManual -Dictionary $_ continue } { $type = $_.GetType() $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } { $_ continue } default { $_ | Select-Object -Property * } } if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue } } $clone } function Get-GitHubLatestRelease { <# .SYNOPSIS Gets one or more releases from GitHub repository .DESCRIPTION Gets one or more releases from GitHub repository .PARAMETER Url Url to github repository .EXAMPLE Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases" | Format-Table .NOTES General notes #> [CmdLetBinding()] param( [parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url ) $ProgressPreference = 'SilentlyContinue' $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1 if ($Responds) { Try { [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json) foreach ($JsonContent in $JsonOutput) { [PSCustomObject] @{ PublishDate = [DateTime] $JsonContent.published_at CreatedDate = [DateTime] $JsonContent.created_at PreRelease = [bool] $JsonContent.prerelease Version = [version] ($JsonContent.name -replace 'v', '') Tag = $JsonContent.tag_name Branch = $JsonContent.target_commitish Errors = '' } } } catch { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = $_.Exception.Message } } } else { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = "No connection (ping) to $($Url.Host)" } } $ProgressPreference = 'Continue' } function Test-ComputerPort { [CmdletBinding()] param ( [alias('Server')][string[]] $ComputerName, [int[]] $PortTCP, [int[]] $PortUDP, [int]$Timeout = 5000 ) begin { if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } } process { foreach ($Computer in $ComputerName) { foreach ($P in $PortTCP) { $Output = [ordered] @{ 'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'TCP' 'Status' = $null 'Summary' = $null 'Response' = $null } $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue if ($TcpClient.TcpTestSucceeded) { $Output['Status'] = $TcpClient.TcpTestSucceeded $Output['Summary'] = "TCP $P Successful" } else { $Output['Status'] = $false $Output['Summary'] = "TCP $P Failed" $Output['Response'] = $Warnings } [PSCustomObject]$Output } foreach ($P in $PortUDP) { $Output = [ordered] @{ 'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'UDP' 'Status' = $null 'Summary' = $null } $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P) $UdpClient.Client.ReceiveTimeout = $Timeout $Encoding = [System.Text.ASCIIEncoding]::new() $byte = $Encoding.GetBytes("Evotec") [void]$UdpClient.Send($byte, $byte.length) $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0) try { $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint) [string]$Data = $Encoding.GetString($Bytes) If ($Data) { $Output['Status'] = $true $Output['Summary'] = "UDP $P Successful" $Output['Response'] = $Data } } catch { $Output['Status'] = $false $Output['Summary'] = "UDP $P Failed" $Output['Response'] = $_.Exception.Message } $UdpClient.Close() $UdpClient.Dispose() [PSCustomObject]$Output } } } end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } } } function Test-WinRM { [CmdletBinding()] param ( [alias('Server')][string[]] $ComputerName ) $Output = foreach ($Computer in $ComputerName) { $Test = [PSCustomObject] @{ Output = $null Status = $null ComputerName = $Computer } try { $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop $Test.Status = $true } catch { $Test.Status = $false } $Test } $Output } $Script:ReportMailbox = [ordered] @{ Name = 'Mailbox' Enabled = $true Execute = { Get-MyMailbox } Processing = { } Summary = { } Variables = @{ } Solution = { New-HTMLTable -DataTable $Script:Reporting['Mailbox']['Data'] -Filtering { } } } $Script:ReportMailboxProblems = [ordered] @{ Name = 'MailboxProblems' Enabled = $true Execute = { Get-MyMailboxProblems -Local } Processing = { } Summary = { } Variables = @{ } Solution = { if ($Script:Reporting['MailboxProblems']['Data'] -is [System.Collections.IDictionary]) { New-HTMLTabPanel -Theme forge { New-HTMLTab -Name "Duplicate Alias" { New-HTMLSection -HeaderText 'Exchange Online - Duplicate Alias' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['DuplicateAlias'] -Filtering } New-HTMLSection -HeaderText 'Exchange On-Premises - Duplicate Alias' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['DuplicateAlias'] -Filtering } New-HTMLSection -HeaderText 'Exchange - Duplicate Alias (Together)' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Both']['DuplicateAlias'] -Filtering } } New-HTMLTab -Name "Duplicate Account" { New-HTMLSection -HeaderText 'Exchange - Duplicate Account' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Both']['DuplicateAccount'] -Filtering } } New-HTMLTab -Name 'No Database' { New-HTMLSection -HeaderText 'Exchange On-Premises - No Database' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['NoDatabase'] -Filtering } } New-HTMLTab -Name 'Inconsistent Data' { New-HTMLSection -HeaderText 'Exchange Online - Broken DisplayName' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['BrokenDisplayName'] -Filtering } New-HTMLSection -HeaderText 'Exchange On-Premises - Broken DisplayName' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['BrokenDisplayName'] -Filtering } New-HTMLSection -HeaderText 'Exchange On-Premises - Missing UserPrincipalName' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['MissingUserPrincipalName'] -Filtering } } New-HTMLTab -Name 'Contact Problems' { New-HTMLSection -HeaderText 'Exchange Online - Contact Missing PrimarySmtp' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['ContactMissingPrimarySmtp'] -Filtering } New-HTMLSection -HeaderText 'Exchange On-Premises - Contact Missing PrimarySmtp' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['ContactMissingPrimarySmtp'] -Filtering } New-HTMLSection -HeaderText 'Exchange Online - Contact Missing ExternalEmail' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Online']['ContactMissingExternalEmail'] -Filtering } New-HTMLSection -HeaderText 'Exchange On-Premises - Contact Missing ExternalEmail' { New-HTMLTable -DataTable $Script:Reporting['MailboxProblems']['Data']['Local']['ContactMissingExternalEmail'] -Filtering } } } } } } function ConvertTo-ExchangeAccessRights { [CmdletBinding()] Param ( [parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][PSObject] $Object, [string] $Identity, [string] $AccessRights = 'FullAccess' ) Process { [PSCustomObject]@{ Identity = $Identity Trustee = $Object.PrimarySmtpAddress AccessControlType = 'Allow' AccessRights = @($AccessRights) IsInherited = $false InheritanceType = 'None' } } } function Get-ExchangeMailboxPermission { [CmdletBinding()] param( [System.Collections.IDictionary] $ReversedPermissions, [System.Collections.IDictionary] $CacheType, $Mailbox, [switch] $Local, [switch] $ExpandGroupMembership ) if ($Local) { $Permissions = Get-LocalMailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false } else { $Permissions = Get-MailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false } foreach ($Permission in $Permissions) { if ($Permission.Deny -eq $false) { if ($Permission.User -ne 'NT AUTHORITY\SELF') { if ($Local) { # Temporary without Expanding Groups if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { $UserSplit = $Permission.User.Split("\") $CurrentUser = $UserSplit[1] } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { $CurrentUser = $CacheNames[$Permission.User] } if ($CurrentUser) { foreach ($Right in $Permission.AccessRights) { if ($Right -like 'FullAccess*') { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias) } } } } else { foreach ($Right in $Permission.AccessRights) { if ($Right -like 'FullAccess*') { if ($Permission.User -like "*@*") { if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { $UserSplit = $Permission.User.Split("\") $CurrentUser = $UserSplit[1] } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { $CurrentUser = $CacheNames[$Permission.User] } if ($CurrentUser) { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias) } } else { if ($ExpandGroupMembership) { if ($Permission.User) { $GroupMembers = Get-MyMailboxMembers -Identity $Permission.User -ErrorAction SilentlyContinue -Verbose:$false -Local:$Local.IsPresent | ConvertTo-ExchangeAccessRights -AccessRights 'FullAccess' -Identity $Permission.Identity foreach ($Member in $GroupMembers) { if ($Member.Trustee) { $CurrentUser = $CacheNames[$Member.Trustee] if ($CurrentUser) { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias) } } } } } } } } } } } } } function Get-MyMailboxSendAs { <# .SYNOPSIS Short function that returns Send-As permissions for mailbox. .DESCRIPTION Function that returns Send-As permissions for mailbox. It's replacement of Get-ADPermission cmdlet that is very slow and inefficient. .PARAMETER ADUser Active Directory user object .PARAMETER Identity DistinguishedName of mailbox .EXAMPLE Get-ADUser -Identity 'przemyslaw.klys' -Properties NtsecurityDescriptor | Get-MyMailboxSendAs .EXAMPLE $Mailbox = Get-Mailbox -Identity 'przemyslaw.klys' $Test = Get-MyMailboxSendAs -Identity $Mailbox.DistinguishedName $Test | Format-Table .NOTES General notes #> [CmdletBinding()] param( [parameter(Mandatory, ParameterSetName = 'ADUser', ValueFromPipeline, ValueFromPipelineByPropertyName)][Microsoft.ActiveDirectory.Management.ADAccount] $ADUser, [parameter(Mandatory, ParameterSetName = 'Identity', ValueFromPipeline, ValueFromPipelineByPropertyName )][string] $Identity ) process { if ($ADUser) { if (-not $ADUser.NtsecurityDescriptor) { Write-Warning -Message "Get-MyMailboxSendAs - Identity '$($ADUser.SamAccountName)' does not have ntSecurityDescriptor attribute. Please provide one or use Identity parameter." return } } else { if (-not $Script:ForestInformation) { $Script:ForestInformation = Get-WinADForestDetails } $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $Identity -ToDomainCN $QueryServer = $Script:ForestInformation['QueryServers'][$DomainName].HostName[0] $ADUser = Get-ADUser -Identity $Identity -Properties ntSecurityDescriptor -Server $QueryServer } $ExtendedPermissions = foreach ($Permission in $ADUser.NtsecurityDescriptor.Access) { # filter out extended right and object type (Send-As) if ($Permission.ActiveDirectoryRights -eq 'ExtendedRight' -and $Permission.objectType -eq "ab721a54-1e2f-11d0-9819-00aa0040529b") { [PSCustomObject] @{ User = $Permission.IdentityReference Identity = if ($ADUser.CanonicalName) { $ADUser.CanonicalName } else { $ADUser.DistinguishedName } AccessRights = 'Send-As' Deny = $Permission.AccessControlType -ne 'Allow' IsInherited = $Permission.IsInherited } } } $ExtendedPermissions } } function New-HTMLReportExchangeEssentials { [cmdletBinding()] param( [Array] $Type, [switch] $Online, [switch] $HideHTML, [string] $FilePath ) New-HTML -Author 'Przemysław Kłys' -TitleText 'ExchangeEssentials Report' { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text "ExchangeEssentials - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } if ($Type.Count -eq 1) { foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) { if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary'] } & $Script:ExchangeEssentialsConfiguration[$T]['Solution'] } } } else { foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) { if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary'] } New-HTMLTab -Name $Script:ExchangeEssentialsConfiguration[$T]['Name'] { & $Script:ExchangeEssentialsConfiguration[$T]['Solution'] } } } } } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath } function New-HTMLReportExchangeEssentialsWithSplit { [cmdletBinding()] param( [Array] $Type, [switch] $Online, [switch] $HideHTML, [string] $FilePath, [string] $CurrentReport ) # Split reports into multiple files for easier viewing $DateName = $(Get-Date -f yyyy-MM-dd_HHmmss) $FileName = [io.path]::GetFileNameWithoutExtension($FilePath) $DirectoryName = [io.path]::GetDirectoryName($FilePath) foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true -and ((-not $CurrentReport) -or ($CurrentReport -and $CurrentReport -eq $T))) { $NewFileName = $FileName + '_' + $T + "_" + $DateName + '.html' $FilePath = [io.path]::Combine($DirectoryName, $NewFileName) New-HTML -Author 'Przemysław Kłys' -TitleText "ExchangeEssentials $CurrentReport Report" { New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow New-HTMLPanelStyle -BorderRadius 0px New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin New-HTMLHeader { New-HTMLSection -Invisible { New-HTMLSection { New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue } -JustifyContent flex-start -Invisible New-HTMLSection { New-HTMLText -Text "ExchangeEssentials - $($Script:Reporting['Version'])" -Color Blue } -JustifyContent flex-end -Invisible } } if ($Script:ExchangeEssentialsConfiguration[$T]['Summary']) { $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Summary'] } & $Script:ExchangeEssentialsConfiguration[$T]['Solution'] } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath } } } function Reset-ExchangeEssentialsStatus { [cmdletBinding()] param( ) if (-not $Script:DefaultTypes) { $Script:DefaultTypes = foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T].Enabled) { $T } } } else { foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T]) { $Script:ExchangeEssentialsConfiguration[$T]['Enabled'] = $false } } foreach ($T in $Script:DefaultTypes) { if ($Script:ExchangeEssentialsConfiguration[$T]) { $Script:ExchangeEssentialsConfiguration[$T]['Enabled'] = $true } } } } $Script:ExchangeEssentialsConfiguration = [ordered] @{ Mailbox = $Script:ReportMailbox MailboxProblems = $Script:ReportMailboxProblems } function Get-MyMailbox { [CmdletBinding()] param( [switch] $IncludeStatistics, [switch] $IncludeCAS, [switch] $IncludeMailUsers, [switch] $Local, [int] $LimitProcessing, [switch] $SkipPermissions, [switch] $ExpandGroupMembership ) $ReversedPermissions = [ordered] @{} $CacheMailbox = [ordered] @{} $CacheCasMailbox = [ordered] @{} $CacheNames = [ordered] @{} #$FinalOutput = [ordered] @{} $CacheType = [ordered] @{} $CacheTypeMailUser = [ordered] @{} $CacheExternalEmails = [ordered] @{} $CacheContacts = [ordered] @{} $CacheContactsLocal = [ordered] @{} $CacheRemoteDomains = [ordered] @{} $CacheRecipientPermissions = [ordered] @{} Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes' $Mailboxes = @( if ($Local) { $TimeLog = Start-TimeLog Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes (Local)' try { $LocalMailboxes = Get-LocalMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Local) took $($EndTimeLog)" } catch { $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Local) took $($EndTimeLog)" Write-Warning -Message "Get-MyMailbox - Unable to get Mailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } foreach ($Mailbox in $LocalMailboxes) { $CacheNames[$Mailbox.UserPrincipalName] = $Mailbox.Alias $CacheNames[$Mailbox.Identity] = $Mailbox.Alias $CacheNames[$Mailbox.Alias] = $Mailbox.Alias $CacheType[$Mailbox.Alias] = 'On-Premises Mailbox' if ($Mailbox.PrimarySmtpAddress) { $CacheExternalEmails[$Mailbox.PrimarySmtpAddress] = $Mailbox.Alias } $Mailbox } } Write-Verbose -Message 'Get-MyMailbox - Getting Mailboxes (Online)' $TimeLog = Start-TimeLog try { $getEXOMailboxSplat = @{ Properties = 'GrantSendOnBehalfTo', 'ForwardingSmtpAddress', 'RecipientTypeDetails', 'SamAccountName', 'WhenCreated', 'WhenMailboxCreated', 'HiddenFromAddressListsEnabled', 'ForwardingAddress', 'AccountDisabled' ResultSize = 'unlimited' ErrorAction = 'Stop' } if ($Identity) { $getEXOMailboxSplat.Identity = $Identity } $OnlineMailboxes = Get-EXOMailbox @getEXOMailboxSplat -Verbose:$false $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Online) took $($EndTimeLog) seconds" } catch { $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose -Message "Get-MyMailbox - Getting Mailboxes (Online) took $($EndTimeLog) seconds" Write-Warning -Message "Get-MyMailbox - Unable to get Mailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } foreach ($Mailbox in $OnlineMailboxes) { $CacheNames[$Mailbox.UserPrincipalName] = $Mailbox.Alias $CacheNames[$Mailbox.Identity] = $Mailbox.Alias $CacheNames[$Mailbox.Alias] = $Mailbox.Alias $CacheType[$Mailbox.Alias] = 'Online Mailbox' $Mailbox } if ($IncludeMailUsers -and -not $Identity) { Write-Verbose -Message 'Get-MyMailbox - Getting MailUsers (Online)' $TimeLog = Start-TimeLog try { $getMailUserSplat = @{ ResultSize = 'unlimited' ErrorAction = 'Stop' } #if ($Identity) { # $getMailUserSplat.Identity = $Identity #} $MailUsersOnline = Get-MailUser @getMailUserSplat foreach ($M in $MailUsersOnline) { if ($M.WindowsEmailAddress -or $M.PrimarySmtpAddress) { if (-not $CacheExternalEmails[$M.WindowsEmailAddress] -or -not $CacheExternalEmails[$M.PrimarySmtpAddress]) { if (-not $CacheExternalEmails[$M.WindowsEmailAddress]) { $CacheExternalEmails[$M.WindowsEmailAddress] = $M.Alias } if (-not $CacheExternalEmails[$M.PrimarySmtpAddress]) { $CacheExternalEmails[$M.PrimarySmtpAddress] = $M.Alias } $CacheNames[$M.PrimarySmtpAddress] = $M.Alias $CacheNames[$M.UserPrincipalName] = $M.Alias $CacheNames[$M.Identity] = $M.Alias $CacheNames[$M.Alias] = $M.Alias $CacheTypeMailUser[$M.Alias] = 'MailUser' #$CacheType[$M.Alias] = 'MailUser' $M } } } } catch { Write-Warning -Message "Get-MyMailbox - Unable to get MailUsers. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner Write-Verbose -Message "Get-MyMailbox - Getting MailUsers (Online) took $($EndTimeLog) seconds" } ) if (-not $SkipPermissions) { Write-Verbose -Message 'Get-MyMailbox - Getting RecipientPermission' try { $RecipientPermissions = Get-EXORecipientPermission -ResultSize Unlimited -ErrorAction Stop -Verbose:$false } catch { Write-Warning -Message "Get-MyMailbox - Unable to get Recipient Permissions. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } } if ($Local) { Write-Verbose -Message 'Get-MyMailbox - Getting Local Mail Contacts' try { [Array] $ContactsLocal = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop -Verbose:$false } catch { Write-Warning -Message "Get-MyMailbox - Unable to get Local Mail Contacts. Error: $($_.Exception.Message.Replace("`r`n", " "))" } } Write-Verbose -Message 'Get-MyMailbox - Getting Mail Contacts' try { [Array] $Contacts = Get-MailContact -ResultSize Unlimited -ErrorAction Stop -Verbose:$false } catch { Write-Warning -Message "Get-MyMailbox - Unable to get Mail Contacts. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } try { $RemoteDomains = @( Write-Verbose -Message 'Get-MyMailbox - Getting Remote Domains' Get-RemoteDomain -ResultSize Unlimited -ErrorAction Stop -Verbose:$false if ($Local) { Write-Verbose -Message 'Get-MyMailbox - Getting Local Remote Domains' Get-LocalRemoteDomain -ErrorAction Stop -Verbose:$false } ) } catch { Write-Warning -Message "Get-MyMailbox - Unable to get Remote Domains. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } Write-Verbose -Message 'Get-MyMailbox - Caching Objects' foreach ($C in $Contacts) { $CacheContacts[$C.Identity] = $C } foreach ($C in $ContactsLocal) { $CacheContactsLocal[$C.Identity] = $C } foreach ($Domain in $RemoteDomains) { $CacheRemoteDomains[$Domain.DomainName] = $Domain } foreach ($RecipientPermission in $RecipientPermissions) { if ($RecipientPermission.Trustee -ne 'NT AUTHORITY\SELF') { if (-not $CacheRecipientPermissions[$RecipientPermission.Identity]) { $CacheRecipientPermissions[$RecipientPermission.Identity] = [System.Collections.Generic.List[PSCustomobject]]::new() } if ($ExpandGroupMembership) { if ($RecipientPermission.Trustee -like "*@*") { $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission) } else { if ($RecipientPermission.Trustee) { $GroupMembers = Get-MyMailboxMembers -Identity $RecipientPermission.Trustee -ErrorAction SilentlyContinue -Verbose:$false -Local:$false | ConvertTo-ExchangeAccessRights -AccessRights SendAs -Identity $RecipientPermission.Identity if ($GroupMembers) { foreach ($Member in $GroupMembers) { #Write-Verbose -Message "Get-MyMailbox - Expanding group and adding Member $($Member.Trustee) to $($RecipientPermission.Identity)" $CacheRecipientPermissions[$RecipientPermission.Identity].Add($Member) } } else { $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission) } } } } else { $CacheRecipientPermissions[$RecipientPermission.Identity].Add($RecipientPermission) } } } if ($IncludeCAS) { Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes' try { if (-not $CasMailboxes) { $CasMailboxes = @( Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes (Online)' Get-CasMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false if ($Local) { Write-Verbose -Message 'Get-MyMailbox - Getting CAS Mailboxes (Local)' Get-LocalCasMailbox -ResultSize unlimited -ErrorAction Stop -Verbose:$false } ) } } catch { Write-Warning -Message "Get-MyMailbox - Unable to get CasMailboxes. Error: $($_.Exception.Message.Replace("`r`n", " "))" return } foreach ($Mailbox in $CasMailboxes) { $CacheCasMailbox[$Mailbox.Identity] = $Mailbox } } $Count = 0 $FilterdMailboxes = @( if ($LimitProcessing) { $LocalMailboxes | Select-Object -First $LimitProcessing $OnlineMailboxes | Select-Object -First $LimitProcessing if ($IncludeMailUsers) { $MailUsersOnline | Select-Object -First $LimitProcessing } } else { $Mailboxes } ) if (-not $SkipPermissions) { Write-Verbose -Message 'Get-MyMailbox - Getting Permissions' foreach ($Mailbox in $FilterdMailboxes) { $Count++ Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName)" $TimeLog = Start-TimeLog $CacheMailbox[$Mailbox.Alias] = [ordered] @{ Mailbox = $Mailbox } #$TimeLogPermissions = Start-TimeLog if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { try { Write-Verbose -Message "Get-MyMailbox - Getting MailboxPermissions for $($Mailbox.Alias) - Local" #$CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-LocalMailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false $CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-ExchangeMailboxPermission -Local -ReversedPermissions $ReversedPermissions -CacheType $CacheType -Mailbox $Mailbox -ExpandGroupMembership:$ExpandGroupMembership.IsPresent } catch { Write-Warning -Message "Get-MyMailbox - Unable to get MailboxPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))" } } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { try { Write-Verbose -Message "Get-MyMailbox - Getting MailboxPermissions for $($Mailbox.Alias) - Online" #$CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-MailboxPermission -Identity $Mailbox.Alias -ErrorAction Stop -Verbose:$false $CacheMailbox[$Mailbox.Alias].MailboxPermissions = Get-ExchangeMailboxPermission -ReversedPermissions $ReversedPermissions -CacheType $CacheType -Mailbox $Mailbox -ExpandGroupMembership:$ExpandGroupMembership.IsPresent } catch { Write-Warning -Message "Get-MyMailbox - Unable to get MailboxPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))" } } if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { try { Write-Verbose -Message "Get-MyMailbox - Getting MailboxADPermissions for $($Mailbox.Alias) - Local" $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions = Get-MyMailboxSendAs -Identity $Mailbox.DistinguishedName #Get-LocalADPermission -Identity $Mailbox.Identity -ErrorAction Stop } catch { Write-Warning -Message "Get-MyMailbox - Unable to get MailboxADPermissions for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))" } } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { if ($CacheRecipientPermissions[$Mailbox.Identity] -and $CacheRecipientPermissions[$Mailbox.Identity].Count -gt 0) { $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions = $CacheRecipientPermissions[$Mailbox.Identity] } } if ($IncludeStatistics) { if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { $CacheMailbox[$Mailbox.Alias]['Statistics'] = Get-LocalMailboxStatistics -Identity $Mailbox.Alias } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { $CacheMailbox[$Mailbox.Alias]['Statistics'] = Get-MailboxStatistics -Identity $Mailbox.Alias } if ($Mailbox.ArchiveDatabase) { try { if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { $Archive = Get-LocalMailboxStatistics -Identity ($Mailbox.Guid).ToString() -Archive -Verbose:$false -ErrorAction Stop } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { $Archive = Get-MailboxStatistics -Identity ($Mailbox.Guid).ToString() -Archive -Verbose:$false -ErrorAction Stop } $CacheMailbox[$Mailbox.Alias]['StatisticsArchive'] = $Archive } catch { Write-Warning -Message "Get-MyMailbox - Unable to get ArchiveStatistics for $($Mailbox.Alias). Error: $($_.Exception.Message.Replace("`r`n", " "))" } } } <# foreach ($Permission in $CacheMailbox[$Mailbox.Alias].MailboxPermissions) { if ($Permission.Deny -eq $false) { if ($Permission.User -ne 'NT AUTHORITY\SELF') { if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { $UserSplit = $Permission.User.Split("\") $CurrentUser = $UserSplit[1] } elseif ($CacheType[$Mailbox.Alias] -eq 'Online Mailbox') { $CurrentUser = $CacheNames[$Permission.User] } if ($CurrentUser) { foreach ($Right in $Permission.AccessRights) { if ($Right -like 'FullAccess*') { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].FullAccess.Add($Mailbox.Alias) } } } else { # Write-Warning -Message "Unable to process $($Permission.User) for $($Mailbox.Alias)" } } } } #> # Process SendAs permissions into ReversedPermissions foreach ($Permission in $CacheMailbox[$Mailbox.Alias].MailboxRecipientPermissions) { if ($CacheType[$Mailbox.Alias] -eq 'On-Premises Mailbox') { if ($Permission.Deny -eq $false -and $Permission.Inherited -eq $false) { if ($Permission.User -ne 'NT AUTHORITY\SELF') { $UserSplit = $Permission.User.Split("\") $CurrentUser = $UserSplit[1] if ($CurrentUser) { foreach ($Right in $Permission.AccessRights) { if (($Right -like 'Send*')) { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].SendAs.Add($Mailbox.Alias) } } } else { #Write-Warning -Message "Unable to process $($Permission.Trustee) for $($Mailbox.Alias)" } } } } else { if ($Permission.AccessControlType -eq 'Allow') { if ($Permission.Trustee -ne 'NT AUTHORITY\SELF') { $CurrentUser = $CacheNames[$Permission.Trustee] if ($CurrentUser) { foreach ($Right in $Permission.AccessRights) { if (($Right -like 'Send*')) { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].SendAs.Add($Mailbox.Alias) } } } else { #Write-Warning -Message "Unable to process $($Permission.Trustee) for $($Mailbox.Alias)" } } } } } # Process SendOnBehalf permissions into ReversedPermissions foreach ($Permission in $CacheMailbox[$Mailbox.Alias].Mailbox.GrantSendOnBehalfTo) { $CurrentUser = $CacheNames[$Permission] if ($CurrentUser) { if (-not $ReversedPermissions[$CurrentUser]) { $ReversedPermissions[$CurrentUser] = [ordered] @{ FullAccess = [System.Collections.Generic.List[string]]::new() SendAs = [System.Collections.Generic.List[string]]::new() SendOnBehalf = [System.Collections.Generic.List[string]]::new() } } $ReversedPermissions[$CurrentUser].SendOnBehalf.Add($Mailbox.Alias) } else { # Write-Warning -Message "Unable to process $($Permission) for $($Mailbox.Alias)" } } #$TimeLogProcessingEnd = Stop-TimeLog -Time $TimeLogProcessing $EndTimeLog = Stop-TimeLog -Time $TimeLog -Option OneLiner #Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName) - [$TimeLogPermissionsEnd][$TimeLogRecipientEnd][$TimeLogStatsEnd][$TimeLogProcessingEnd]" Write-Verbose -Message "Processing Mailbox $Count/$($Mailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName) - [$EndTimeLog]" } } Write-Verbose -Message 'Get-MyMailbox - Reversing Permissions' foreach ($Alias in $ReversedPermissions.Keys) { if ($CacheMailbox[$Alias]) { $CacheMailbox[$Alias].FullAccess = $ReversedPermissions[$Alias].FullAccess $CacheMailbox[$Alias].SendAs = $ReversedPermissions[$Alias].SendAs $CacheMailbox[$Alias].SendOnBehalf = $ReversedPermissions[$Alias].SendOnBehalf } } Write-Verbose -Message 'Get-MyMailbox - Processing Mailboxes' $Count = 0 foreach ($Mailbox in $FilterdMailboxes) { $Count++ Write-Verbose -Message "Processing Mailbox $Count/$($FilterdMailboxes.Count) - $($Mailbox.Alias) / $($Mailbox.UserPrincipalName) / $($Mailbox.DisplayName)" # lets reset the variables $ForwardAddress = $null $IsForward = $false $ForwardingType = $null $ForwardingStatus = $null # establish forwarding status if ($Mailbox.RecipientType -eq 'MailUser') { if ($Mailbox.ExternalEmailAddress) { # the assumption is that the ExternalEmailAddress is the forwarding address, when there are more than 1 email addresses on mailuser $Counter = 0 foreach ($Email in $Mailbox.EmailAddresses) { if ($Email.StartsWith('smtp:', $true, $null)) { $Counter++ } } if ($Counter -gt 1) { $ForwardAddress = Convert-ExchangeEmail -Emails $Mailbox.ExternalEmailAddress -RemovePrefix if ($ForwardAddress) { $IsForward = $true $ForwardingType = 'MailUserForward' $ForwardingStatus = 'External' } } } if (-not $ForwardAddress) { $ForwardAddress = $null $IsForward = $false $ForwardingType = 'None' $ForwardingStatus = 'None' } } else { if ($Mailbox.ForwardingAddress) { $Contact = $CacheContacts[$Mailbox.ForwardingAddress] $ContactLocal = $CacheContactsLocal[$Mailbox.ForwardingAddress] if ($Contact) { $ForwardAddress = Convert-ExchangeEmail -Emails $Contact.ExternalEmailAddress -RemovePrefix if ($ForwardAddress) { $IsForward = $true } else { $IsForward = $false } } elseif ($ContactLocal) { $ForwardAddress = Convert-ExchangeEmail -Emails $ContactLocal.ExternalEmailAddress -RemovePrefix if ($ForwardAddress) { $IsForward = $true } else { $IsForward = $false } } else { $ForwardAddress = $Mailbox.ForwardingAddress $IsForward = $true } $ForwardingType = 'ContactForward' } elseif ($Mailbox.ForwardingSmtpAddress) { $ForwardAddress = Convert-ExchangeEmail -Emails $Mailbox.ForwardingSmtpAddress -RemovePrefix if ($ForwardAddress) { $IsForward = $true } else { # this shouldn't happen $IsForward = 'Unknown' } $ForwardingType = 'MailboxForward' } else { $ForwardAddress = $null $IsForward = $false $ForwardingType = 'None' } } if (-not $ForwardingStatus) { if ($ForwardAddress) { if ($ForwardAddress -like "*@*") { $SplitAddress = $ForwardAddress -split "@" $DomainName = $SplitAddress[1] $DomainName = $DomainName.Trim() if ($CacheRemoteDomains[$DomainName]) { $ForwardingStatus = "Internal" } else { $ForwardingStatus = "External" } } else { $ForwardingStatus = "Unknown" } } else { $ForwardingStatus = 'None' } } $Type = if ($CacheType[$Mailbox.Alias]) { $CacheType[$Mailbox.Alias] } elseif ($CacheTypeMailUser[$Mailbox.Alias]) { $CacheTypeMailUser[$Mailbox.Alias] } if (-not $SkipPermissions) { $User = [ordered] @{ DisplayName = $Mailbox.DisplayName Alias = $Mailbox.Alias UserPrincipalName = $Mailbox.UserPrincipalName Enabled = if ($Mailbox.AccountDisabled -eq $true) { $false } elseif ($Mailbox.AccountDisabled -eq $false) { $true } else { $null } Type = $Type TypeDetails = $Mailbox.RecipientTypeDetails PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress SamAccountName = $Mailbox.SamAccountName ForwardingEnabled = $IsForward ForwardingStatus = $ForwardingStatus ForwardingType = $ForwardingType ForwardingAddress = $ForwardAddress FullAccess = $CacheMailbox[$Mailbox.Alias].FullAccess SendAs = $CacheMailbox[$Mailbox.Alias].SendAs SendOnBehalf = $CacheMailbox[$Mailbox.Alias].SendOnBehalf FullAccessCount = $CacheMailbox[$Mailbox.Alias].FullAccess.Count SendAsCount = $CacheMailbox[$Mailbox.Alias].SendAs.Count SendOnBehalfCount = $CacheMailbox[$Mailbox.Alias].SendOnBehalf.Count WhenCreated = $Mailbox.WhenCreated WhenMailboxCreated = $Mailbox.WhenMailboxCreated HiddenFromAddressListsEnabled = $Mailbox.HiddenFromAddressListsEnabled } } else { $User = [ordered] @{ DisplayName = $Mailbox.DisplayName Alias = $Mailbox.Alias UserPrincipalName = $Mailbox.UserPrincipalName Enabled = if ($Mailbox.AccountDisabled -eq $true) { $false } elseif ($Mailbox.AccountDisabled -eq $false) { $true } else { $null } Type = $Type TypeDetails = $Mailbox.RecipientTypeDetails PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress SamAccountName = $Mailbox.SamAccountName ForwardingEnabled = $IsForward ForwardingStatus = $ForwardingStatus ForwardingType = $ForwardingType ForwardingAddress = $ForwardAddress WhenCreated = $Mailbox.WhenCreated WhenMailboxCreated = $Mailbox.WhenMailboxCreated HiddenFromAddressListsEnabled = $Mailbox.HiddenFromAddressListsEnabled } } if ($IncludeStatistics) { #$User['LastUserAccessTime'] = $CacheMailbox[$Mailbox.Alias].Statistics.LastUserAccessTime $User['LastLogonTime'] = $CacheMailbox[$Mailbox.Alias].Statistics.LastLogonTime $User['TotalItems'] = $CacheMailbox[$Mailbox.Alias].Statistics.ItemCount $User['TotalGB'] = Convert-ExchangeSize -Size $CacheMailbox[$Mailbox.Alias].Statistics.TotalItemSize -To GB $User['TotalArchiveItems'] = $CacheMailbox[$Mailbox.Alias].StatisticsArchive.ItemCount $User['TotalArchiveGB'] = Convert-ExchangeSize -Size $CacheMailbox[$Mailbox.Alias].StatisticsArchive.TotalItemSize -To GB } if ($IncludeCAS) { $CasProperties = @( 'ActiveSyncEnabled' 'OWAEnabled' 'OWAforDevicesEnabled' 'ECPEnabled' 'PopEnabled' 'PopMessageDeleteEnabled' 'ImapEnabled' 'MAPIEnabled' 'MapiHttpEnabled' 'UniversalOutlookEnabled' 'OutlookMobileEnabled' 'MacOutlookEnabled' 'EwsEnabled' 'OneWinNativeOutlookEnabled' 'BulkMailEnabled' 'SmtpClientAuthenticationDisabled' ) if ($CacheCasMailbox[$Mailbox.Identity]) { foreach ($Property in $CasProperties) { $User[$Property] = $CacheCasMailbox[$Mailbox.Alias].$Property } } else { foreach ($Property in $CasProperties) { $User[$Property] = $null } } } [PSCustomObject] $User #$FinalOutput[$Mailbox.Alias] = $ConvertedUser #$ConvertedUser } } function Get-MyMailboxMembers { [alias('Get-ExchangeMembersRecursive', 'Get-MyMailboxMembersRecursive')] [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline)] $Identity, [switch] $Local, [Parameter(DontShow)][int] $Nesting = -1, [Parameter(DontShow)][switch] $Nested ) Begin { $IgnoreAccounts = @( 'Everyone' 'NT AUTHORITY\Authenticated Users' "Discovery Management" ) if ($Nesting -eq -1) { $GroupCache = [ordered] @{} } } Process { $Nesting++ $MatchRegex = [Regex]::Matches($Identity, "S-\d-\d+-(\d+-|){1,14}\d+") if ($MatchRegex.Success) { # do nothing } elseif ($Identity -in $IgnoreAccounts) { # do nothing } else { if (-not $GroupCache[$Identity]) { $GroupCache[$Identity] = $true } else { Write-Verbose -Message "Get-MyMailboxMembers - Identity '$Identity' already processed. Circular reference detected." return # Write-Color "Identity '$Identity' already processed." -Color Red #return } Write-Verbose -Message "Processing '$Identity'" if ($Local) { $Members = try { Get-LocalDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop } catch { try { Get-LocalDynamicDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop } catch { Write-Warning -Message "Get-MyMailboxMembers - Identity '$Identity' is not a valid group." } } } else { $Members = try { Get-DistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop } catch { try { Get-DynamicDistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop } catch { Write-Warning -Message "Get-MyMailboxMembers - Identity '$Identity' is not a valid group." } } } foreach ($Member in $Members) { switch ($Member) { { $_.RecipientType -notlike "*Group*" } { $_ } Default { if ($_.primarysmtpAddress) { Get-MyMailboxMembers -Identity $_.primarysmtpAddress -Local:$Local.IsPresent -Nested -Nesting $Nesting } } } } } } End { if (-not $Nested) { #Write-Color -Text "Get-MyMailboxMembers - Clearing GroupCache" -Color Red $GroupCache = $Null } } } function Get-MyMailboxProblems { [CmdletBinding()] param( [switch] $Local ) $Problems = [ordered] @{ Data = [ordered] @{ ContactsLocal = $Null ContactsOnline = $Null MailboxesLocal = $Null MailboxesOnline = $Null } Online = [ordered] @{ BrokenDisplayName = [System.Collections.Generic.List[PSCustomObject]]::new() DuplicateAlias = [System.Collections.Generic.List[PSCustomObject]]::new() NoDatabase = [System.Collections.Generic.List[PSCustomObject]]::new() ContactMissingPrimarySmtp = [System.Collections.Generic.List[PSCustomObject]]::new() ContactMissingExternalEmail = [System.Collections.Generic.List[PSCustomObject]]::new() MissingUserPrincipalName = [System.Collections.Generic.List[PSCustomObject]]::new() } Local = [ordered] @{ BrokenDisplayName = [System.Collections.Generic.List[PSCustomObject]]::new() DuplicateAlias = [System.Collections.Generic.List[PSCustomObject]]::new() NoDatabase = [System.Collections.Generic.List[PSCustomObject]]::new() ContactMissingPrimarySmtp = [System.Collections.Generic.List[PSCustomObject]]::new() ContactMissingExternalEmail = [System.Collections.Generic.List[PSCustomObject]]::new() MissingUserPrincipalName = [System.Collections.Generic.List[PSCustomObject]]::new() } Both = [ordered] @{ DuplicateAlias = [System.Collections.Generic.List[PSCustomObject]]::new() DuplicateAccount = [System.Collections.Generic.List[PSCustomObject]]::new() } } $PropertiesMailbox = @( 'UserPrincipalName', 'Alias', 'DisplayName', 'Database', 'PrimarySmtpAddress', 'RecipientType', 'RecipientTypeDetails', 'ExchangeUserAccountControl' #, 'Identity' ) if ($Local) { try { $ContactsLocal = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop } catch { Write-Warning "Get-MyMailboxProblems - Unable to get local contacts: $($_.Exception.Message)" $ContactsLocal = @() } try { $MailboxesLocal = Get-LocalMailbox -ResultSize Unlimited | Select-Object -Property $PropertiesMailbox -ErrorAction Stop } catch { Write-Warning "Get-MyMailboxProblems - Unable to get local mailboxes: $($_.Exception.Message)" $MailboxesLocal = @() } } try { $ContactsOnline = Get-LocalMailContact -ResultSize Unlimited -ErrorAction Stop } catch { Write-Warning "Get-MyMailboxProblems - Unable to get online contacts: $($_.Exception.Message)" $ContactsOnline = @() } try { $MailboxesOnline = Get-EXOMailbox -ResultSize Unlimited -Properties $PropertiesMailbox | Select-Object -Property $PropertiesMailbox -ErrorAction Stop } catch { Write-Warning "Get-MyMailboxProblems - Unable to get online mailboxes: $($_.Exception.Message)" $MailboxesOnline = @() } foreach ($C in $ContactsLocal) { if (-not $C.PrimarySmtpAddress) { $Problems.Local.ContactMissingPrimarySmtp.Add($C) } if (-not $C.ExternalEmailAddress) { $Problems.Local.ContactMissingExternalEmail.Add($C) } } foreach ($C in $ContactsOnline) { if (-not $C.PrimarySmtpAddress) { $Problems.Online.ContactMissingPrimarySmtp.Add($C) } if (-not $C.ExternalEmailAddress) { $Problems.Online.ContactMissingExternalEmail.Add($C) } } foreach ($M in $MailboxesLocal) { if ($M.DisplayName.StartsWith(' ') -or $M.DisplayName.EndsWith(' ')) { $Problems.Local.BrokenDisplayName.Add($M) } if ($Null -eq $M.Database) { $Problems.Local.NoDatabase.Add($M) } if ($null -eq $M.UserPrincipalName) { $Problems.Local.MissingUserPrincipalName.Add($M) } } foreach ($M in $MailboxesOnline) { if ($M.DisplayName.StartsWith(' ') -or $M.DisplayName.EndsWith(' ')) { $Problems.Online.BrokenDisplayName.Add($M) } if ($Null -eq $M.Database) { $Problems.Online.NoDatabase.Add($M) } if ($null -eq $M.UserPrincipalName) { $Problems.Local.MissingUserPrincipalName.Add($M) } } $CacheAll = [ordered] @{} $Cache = [ordered] @{} foreach ($M in $MailboxesOnline) { if (-not $Cache[$M.Alias]) { $Cache[$M.Alias] = $M } else { if (-not $CacheAll[$M.Alias]) { $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new() $CacheAll[$M.Alias].Add($Cache[$M.Alias]) } $CacheAll[$M.Alias].Add($M) } } foreach ($M in $CacheAll.Values) { $Problems.Online.DuplicateAlias.Add($M) } # lets reset for local $CacheAll = [ordered] @{} $Cache = [ordered] @{} foreach ($M in $MailboxesLocal) { if (-not $Cache[$M.Alias]) { $Cache[$M.Alias] = $M } else { if (-not $CacheAll[$M.Alias]) { $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new() $CacheAll[$M.Alias].Add($Cache[$M.Alias]) } $CacheAll[$M.Alias].Add($M) } } foreach ($M in $CacheAll.Values) { $Problems.Local.DuplicateAlias.Add($M) } # both? $CacheAll = [ordered] @{} $Cache = [ordered] @{} $CacheUPN = [ordered] @{} $CacheAllUPN = [ordered] @{} $Mailboxes = @( if ($MailboxesOnline.Count -gt 0) { $MailboxesOnline } if ($MailboxesLocal.Count -gt 0) { $MailboxesLocal } ) foreach ($M in $Mailboxes) { # Duplicate Accounts if (-not $CacheUPN[$M.PrimarySmtpAddress]) { $CacheUPN[$M.PrimarySmtpAddress] = $M } else { if (-not $CacheAllUPN[$M.PrimarySmtpAddress]) { $CacheAllUPN[$M.PrimarySmtpAddress] = [System.Collections.Generic.List[PSCustomObject]]::new() $CacheAllUPN[$M.PrimarySmtpAddress].Add($CacheUPN[$M.PrimarySmtpAddress]) } $CacheAllUPN[$M.PrimarySmtpAddress].Add($M) } # Duplicate Aliases if (-not $Cache[$M.Alias]) { $Cache[$M.Alias] = $M } else { if (-not $CacheAll[$M.Alias]) { $CacheAll[$M.Alias] = [System.Collections.Generic.List[PSCustomObject]]::new() $CacheAll[$M.Alias].Add($Cache[$M.Alias]) } $CacheAll[$M.Alias].Add($M) } } foreach ($M in $CacheAll.Values) { $Problems.Both.DuplicateAlias.Add($M) } foreach ($M in $CacheAllUPN.Values) { $Problems.Both.DuplicateAccount.Add($M) } if ($Local) { $Problems.Data.ContactsLocal = $ContactsLocal $Problems.Data.MailboxesLocal = $MailboxesLocal } $Problems.Data.ContactsOnline = $ContactsOnline $Problems.Data.MailboxesOnline = $MailboxesOnline $Problems } function Invoke-ExchangeEssentials { [CmdletBinding()] param( [string] $FilePath, [Parameter(Position = 0)][string[]] $Type, [switch] $PassThru, [switch] $HideHTML, [switch] $Online, [switch] $SplitReports ) $Script:Reporting = [ordered] @{} $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-ExchangeEssentials' -RepositoryOwner 'evotecit' -RepositoryName 'ExchangeEssentials' $Script:Reporting['Settings'] = @{ ShowError = $ShowError.IsPresent ShowWarning = $ShowWarning.IsPresent HideSteps = $HideSteps.IsPresent } Write-Color '[i]', "[ExchangeEssentials] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta $Supported = [System.Collections.Generic.List[string]]::new() [Array] $NotSupported = foreach ($T in $Type) { if ($T -notin $Script:ExchangeEssentialsConfiguration.Keys ) { $T } else { $Supported.Add($T) } } if ($Supported) { Write-Color '[i]', "[ExchangeEssentials] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta } if ($NotSupported) { Write-Color '[i]', "[ExchangeEssentials] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta Write-Color '[i]', "[ExchangeEssentials] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:ExchangeEssentialsConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta return } # Lets make sure we only enable those types which are requestd by user if ($Type) { foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { $Script:ExchangeEssentialsConfiguration[$T].Enabled = $false } # Lets enable all requested ones foreach ($T in $Type) { $Script:ExchangeEssentialsConfiguration[$T].Enabled = $true } } # Build data foreach ($T in $Script:ExchangeEssentialsConfiguration.Keys) { if ($Script:ExchangeEssentialsConfiguration[$T].Enabled -eq $true) { $Script:Reporting[$T] = [ordered] @{ Name = $Script:ExchangeEssentialsConfiguration[$T].Name ActionRequired = $null Data = $null Exclusions = $null WarningsAndErrors = $null Time = $null Summary = $null Variables = Copy-Dictionary -Dictionary $Script:ExchangeEssentialsConfiguration[$T]['Variables'] } if ($Exclusions) { if ($Exclusions -is [scriptblock]) { $Script:Reporting[$T]['ExclusionsCode'] = $Exclusions } if ($Exclusions -is [Array]) { $Script:Reporting[$T]['Exclusions'] = $Exclusions } } $TimeLogExchangeEssentials = Start-TimeLog Write-Color -Text '[i]', '[Start] ', $($Script:ExchangeEssentialsConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow $OutputCommand = Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains if ($OutputCommand -is [System.Collections.IDictionary]) { # in some cases the return will be wrapped in Hashtable/orderedDictionary and we need to handle this without an array $Script:Reporting[$T]['Data'] = $OutputCommand } else { # since sometimes it can be 0 or 1 objects being returned we force it being an array $Script:Reporting[$T]['Data'] = [Array] $OutputCommand } Invoke-Command -ScriptBlock $Script:ExchangeEssentialsConfiguration[$T]['Processing'] $Script:Reporting[$T]['WarningsAndErrors'] = @( if ($ShowWarning) { foreach ($War in $CommandWarnings) { [PSCustomObject] @{ Type = 'Warning' Comment = $War Reason = '' TargetName = '' } } } if ($ShowError) { foreach ($Err in $CommandErrors) { [PSCustomObject] @{ Type = 'Error' Comment = $Err Reason = $Err.CategoryInfo.Reason TargetName = $Err.CategoryInfo.TargetName } } } ) $TimeEndExchangeEssentials = Stop-TimeLog -Time $TimeLogExchangeEssentials -Option OneLiner $Script:Reporting[$T]['Time'] = $TimeEndExchangeEssentials Write-Color -Text '[i]', '[End ] ', $($Script:ExchangeEssentialsConfiguration[$T]['Name']), " [Time to execute: $TimeEndExchangeEssentials]" -Color Yellow, DarkGray, Yellow, DarkGray if ($SplitReports) { Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for ', $T -Color Yellow, DarkGray, Yellow $TimeLogHTML = Start-TimeLog New-HTMLReportExchangeEssentialsWithSplit -FilePath $FilePath -Online:$Online -HideHTML:$HideHTML -CurrentReport $T $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report for', $T, " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } } } if ( -not $SplitReports) { Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report' -Color Yellow, DarkGray, Yellow $TimeLogHTML = Start-TimeLog if (-not $FilePath) { $FilePath = Get-FileName -Extension 'html' -Temporary } New-HTMLReportExchangeEssentials -Type $Type -Online:$Online.IsPresent -HideHTML:$HideHTML.IsPresent -FilePath $FilePath $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray } Reset-ExchangeEssentialsStatus if ($PassThru) { $Script:Reporting } } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:ExchangeEssentialsConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName Invoke-ExchangeEssentials -ParameterName Type -ScriptBlock $SourcesAutoCompleter if ($PSVersionTable.PSEdition -eq 'Desktop' -and (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -lt 379893) { Write-Warning "This module requires .NET Framework 4.5.2 or later."; return } # Export functions and aliases as required Export-ModuleMember -Function @('Get-MyMailbox', 'Get-MyMailboxMembers', 'Get-MyMailboxProblems', 'Invoke-ExchangeEssentials') -Alias @('Get-ExchangeMembersRecursive', 'Get-MyMailboxMembersRecursive') # SIG # Begin signature block # MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDGMyEKY2RemsvM # XjtsB4fHB5H+I2MNPnlvAlzbOy7X26CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/ # X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx # MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX # ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj # aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7 # ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB # uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu # 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg # LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG # FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc # ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh # cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2 # 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD # y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW # BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg # hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O # BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy # NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF # BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6 # Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT # SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g # qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s # 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q # BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4 # 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w # QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z # iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn # LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza # ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy # 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA # dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl # G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU # otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw # MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p # a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD # VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV # OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE # h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd # GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0 # 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA # o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw # 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP # 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi # W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK # RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA # BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID # AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD # VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV # HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js # My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw # OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy # MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0 # cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl # U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG # SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50 # ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa # 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2 # CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0 # djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N # 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi # zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38 # wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y # n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z # n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe # 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC # BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB # ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ # AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G # CSqGSIb3DQEJBDEiBCCJw+hPwhdc6Xy2EEvddT4rKeAFCwlCfaWfbd4/J7s7BTAN # BgkqhkiG9w0BAQEFAASCAgBKdXldO2sU/Ai3N7mmx8U6P4dT8RDRCiYsuy5Aq++n # MsgGNrYnxop6w+vCTJbESv4JcsjoUvawlXGlIb5HtGHB758KMQvF64JGvpND2Om/ # uqqupw1tc+s1YLZYT06KbJM9jH8YLPKKecsyyVHg5u9bGFHjd+MyxLw9d9CnO+1S # GcJzFPqaZGHvLxSLrd/UAZvnCqQ7p8OqfQfXJeM+KqBnuaCc9zI6M7/RHdW8rhrX # Bco5iKIPhfccFXaYXdMkP0Vhmu3mWEwz1SQbK5sSt4YEDXNB1uYFF1Q8MVF4MiyQ # 7+s6EO5ae6bO0Cebh5uinpxcqpkJ+zbkn7I4rNNVQ31jVX29wXmCFqy0VxABeQx5 # 0R1G7k1gp6NDHsebpmc3HIEHyKS107lTYcLZVL+b58JtjDuy/OIpPjpL4iVaA8Q4 # t4ENL25IXAs420Om/Guz9qdP4QlKVxEglaIZ/B+pV805y2NlZkIRtsp4KstT46Ob # XP2ysy2Y85MatglNZAHsviDuYmNqbCnGu/hHnS2eWKOc8vj0Kn7iW58uBYoOVTUc # mLU5DJIgMv7MuUFVy1Zso82RPDrVHJAIYHGspl45wUuNOi15nP0Z2pnb92oYSvgW # XgSC5mcDnGl+xTfNCXsaeyVnE9KXdlDpoQdwWGOV+GjYOHeQq4Rfrkfb/1G5zEez # fqGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5 # pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN # AQcBMBwGCSqGSIb3DQEJBTEPFw0yMzEyMTIxMjI0MTBaMC8GCSqGSIb3DQEJBDEi # BCCpIXnHToUUh2e3BC9di4Ig/JRVV3gbjm7NZEga/RZZUDANBgkqhkiG9w0BAQEF # AASCAgCYkHFSbdskfz9Blr/Fzw/KyNzKJ1kJtVi0ET1uc7EpDkSXkpLhO+0i2zBP # OwBpFfdItFH/Nk8pBbG4C7elHUpxrouwKOc0zwzS4Q8C2VFaBjIlAELh4P7zAQmf # TrPljVJ2+yvu4dy0soSAiTAW2z/ZH1H/tuWavpw7sKzJDk8C7Sgru+c34quK85Jo # McP0t8q2auTOjxLwbJL12VVdZXGevq6XOYDlx//9N5gmfNB80bnBzszftTV48jyr # FvqmgYcQ5Trxjj26KGk2OVKF0NtCZuGDmRdtDPXYd4KvrJEygDaHcN5x2u4eLbDH # nYLoqCabredQV2XYycb0LTLWZMgSyZMTbKwIJzew0O2zyq4CpTk/b0KDfKv5uUZj # gcUdugUGp1QJyLaFpOFTkaULr+pOfSxhxwX4UB/F1EE1xWXLaa7bLpStDPwkOUxS # zqelU35G1BBnHxXGXPgSV5+b6TaeCiett23EhyR8eUbkaI6jjOECFW1aI+/4yhQ1 # VhwyKCqup/sU83/hwzrHoyOSkAXAd2bQ0IkxBPAEmpIQ6pd+qggRPy1vffG6j73u # VmUoeLumUhcpsfXY6PTphB02R/vVzcco2Ywqi76Fgvj7tJakuljXkVMSLpuvxUSv # nePcBOQL1Qp27hge/P0yd0399qhFVIZj+6MDpSQkdxB6FR1J0A== # SIG # End signature block |