From 371c609856025aa4d3db1600a0471db84049a9df Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 30 Jul 2021 17:06:29 -0500 Subject: [PATCH 01/10] New iteration of New-LoggerObject A cleaner and easier to understand logic of New-LoggerObject. --- Shared/LoggerFunctions.ps1 | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Shared/LoggerFunctions.ps1 diff --git a/Shared/LoggerFunctions.ps1 b/Shared/LoggerFunctions.ps1 new file mode 100644 index 0000000000..b2a57bfc63 --- /dev/null +++ b/Shared/LoggerFunctions.ps1 @@ -0,0 +1,105 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Function Get-NewLoggerInstance { + [CmdletBinding()] + param( + [ValidateScript( { Test-Path $_ })] + [string]$LogDirectory = (Get-Location).Path, + + [ValidateNotNullOrEmpty()] + [string]$LogName = "Script_Logging", + + [bool]$AppendDateTime = $true, + + [bool]$AppendDateTimeToFileName = $true, + + [int]$MaxFileSizeMB = 10, + + [int]$CheckSizeIntervalMinutes = 10, + + [int]$NumberOfLogsToKeep = 10 + ) + + $fileName = if ($AppendDateTime) { "{0}_{1}.txt" -f $LogName, ((Get-Date).ToString('yyyyMMddHHmmss')) } else { "$LogName.txt" } + $fullFilePath = [System.IO.Path]::Combine($LogDirectory, $fileName) + + return [PSCustomObject]@{ + FullPath = $fullFilePath + AppendDateTime = $AppendDateTime + AppendDateTimeToFileName = $AppendDateTimeToFileName + MaxFileSizeMB = $MaxFileSizeMB + CheckSizeIntervalMinutes = $CheckSizeIntervalMinutes + NumberOfLogsToKeep = $NumberOfLogsToKeep + BaseInstanceFileName = $fileName.Replace(".txt", "") + Instance = 1 + NextFileCheckTime = ((Get-Date).AddMinutes($CheckSizeIntervalMinutes)) + PreventLogCleanup = $false + LoggerDisabled = $false + } | Write-LoggerInstance -Object "Starting Logger Instance $(Get-Date)" +} + +Function Write-LoggerInstance { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [object]$LoggerInstance, + + [Parameter(Mandatory = $true)] + [object]$Object + ) + process { + if ($LoggerInstance.LoggerDisabled) { return } + + if ($LoggerInstance.AppendDateTime -and + $Object.GetType().Name -eq "string") { + $Object = "[$([System.DateTime]::Now)] : $Object" + } + + $Object | Out-File $LoggerInstance.FullPath -Append + + #Upkeep of the logger information + if ($LoggerInstance.NextFileCheckTime -gt [System.DateTime]::Now) { + return + } + + #Set next update time to avoid issues so we can log things + $LoggerInstance.NextFileCheckTime = ([System.DateTime]::Now).AddMinutes($LoggerInstance.CheckSizeIntervalMinutes) + $item = Get-ChildItem $LoggerInstance.FullPath + + if (($item.Length / 1MB) -gt $LoggerInstance.MaxFileSizeMB) { + $LoggerInstance | Write-LoggerInstance -Object "Max file size reached rolling over" | Out-Null + $directory = [System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath) + $fileName = "$($LoggerInstance.BaseInstanceFileName)-$($LoggerInstance.Instance).txt" + $LoggerInstance.Instance++ + $LoggerInstance.FullPath = [System.IO.Path]::Combine($directory, $fileName) + + $items = Get-ChildItem -Path ([System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath)) -Filter "*$($LoggerInstance.BaseInstanceFileName)*" + + if ($items.Count -gt $LoggerInstance.NumberOfLogsToKeep) { + $item = $items | Sort-Object LastWriteTime | Select-Object -First 1 + $LoggerInstance | Write-LoggerInstance "Removing Log File $($item.FullName)" | Out-Null + $item | Remove-Item -Force + } + } + } + end { + return $LoggerInstance + } +} + +Function Invoke-LoggerInstanceCleanup { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [object]$LoggerInstance + ) + + if ($LoggerInstance.LoggerDisabled -or + $LoggerInstance.PreventLogCleanup) { + return + } + + Get-ChildItem -Path ([System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath)) -Filter "*$($LoggerInstance.BaseInstanceFileName)*" | + Remove-Item -Force +} From adcdfef5777784dd0e558295ed34596be423b980 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Fri, 30 Jul 2021 17:23:40 -0500 Subject: [PATCH 02/10] Added Position to avoid passing parameter name with it This only works if `$LoggerInstance` is passed from a pipeline --- Shared/LoggerFunctions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Shared/LoggerFunctions.ps1 b/Shared/LoggerFunctions.ps1 index b2a57bfc63..529819c70b 100644 --- a/Shared/LoggerFunctions.ps1 +++ b/Shared/LoggerFunctions.ps1 @@ -45,7 +45,7 @@ Function Write-LoggerInstance { [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [object]$LoggerInstance, - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, Position = 1)] [object]$Object ) process { From a0a3e2c76a63c066cf0f1ba2049aa1d15bff5d33 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Wed, 4 Aug 2021 17:27:32 +0200 Subject: [PATCH 03/10] Test-ScriptVersion proxy auth work --- Shared/Test-ScriptVersion.ps1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Shared/Test-ScriptVersion.ps1 b/Shared/Test-ScriptVersion.ps1 index e9de98715c..b1716c141b 100644 --- a/Shared/Test-ScriptVersion.ps1 +++ b/Shared/Test-ScriptVersion.ps1 @@ -90,6 +90,11 @@ function Test-ScriptVersion { try { $versionsUrl = "https://github.com/microsoft/CSS-Exchange/releases/latest/download/ScriptVersions.csv" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + if (([System.Net.WebRequest]::GetSystemWebproxy()).IsBypassed("https://github.com") -eq $false) { + $webClient = New-Object System.Net.WebClient + $webClient.Headers.Add("User-Agent", "PowerShell") + $webClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials + } $versionData = [Text.Encoding]::UTF8.GetString((Invoke-WebRequest $versionsUrl -UseBasicParsing).Content) | ConvertFrom-Csv $latestVersion = ($versionData | Where-Object { $_.File -eq $scriptName }).Version if ($null -ne $latestVersion -and $latestVersion -ne $BuildVersion) { From 2475969bbec645c654e79d9d55d120176d8caee8 Mon Sep 17 00:00:00 2001 From: Lukas Sassl Date: Thu, 5 Aug 2021 15:53:37 +0200 Subject: [PATCH 04/10] Function added to validate proxy usage --- Shared/Test-ScriptVersion.ps1 | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Shared/Test-ScriptVersion.ps1 b/Shared/Test-ScriptVersion.ps1 index b1716c141b..a882d681d1 100644 --- a/Shared/Test-ScriptVersion.ps1 +++ b/Shared/Test-ScriptVersion.ps1 @@ -16,6 +16,27 @@ function Test-ScriptVersion { $AutoUpdate ) + function Confirm-ProxyServer { + [CmdletBinding()] + [OutputType([bool])] + param ( + [Parameter(Mandatory = $true)] + [string] + $TargetUri + ) + + try { + $proxyObject = ([System.Net.WebRequest]::GetSystemWebproxy()).GetProxy($TargetUri) + if ($TargetUri -ne $proxyObject.OriginalString) { + return $true + } else { + return $false + } + } catch { + return $false + } + } + function Confirm-Signature { [CmdletBinding()] [OutputType([bool])] @@ -90,7 +111,7 @@ function Test-ScriptVersion { try { $versionsUrl = "https://github.com/microsoft/CSS-Exchange/releases/latest/download/ScriptVersions.csv" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - if (([System.Net.WebRequest]::GetSystemWebproxy()).IsBypassed("https://github.com") -eq $false) { + if (Confirm-ProxyServer -TargetUri "https://github.com") { $webClient = New-Object System.Net.WebClient $webClient.Headers.Add("User-Agent", "PowerShell") $webClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials From dbf1674ccdc68ba4e749a2a32cce1fb900e0b07a Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 5 Aug 2021 11:03:04 -0500 Subject: [PATCH 05/10] Renamed file to match script name --- ...t-SimpleAdminAuditLogReport.md => Get-SimpleAuditLogReport.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/Admin/{Get-SimpleAdminAuditLogReport.md => Get-SimpleAuditLogReport.md} (100%) diff --git a/docs/Admin/Get-SimpleAdminAuditLogReport.md b/docs/Admin/Get-SimpleAuditLogReport.md similarity index 100% rename from docs/Admin/Get-SimpleAdminAuditLogReport.md rename to docs/Admin/Get-SimpleAuditLogReport.md From e25cd2a4eaf8a0aa6c4489430a1ae6d7d3163dd4 Mon Sep 17 00:00:00 2001 From: David Paulson Date: Thu, 5 Aug 2021 11:05:59 -0500 Subject: [PATCH 06/10] Corrected name from SimpleAdminAuditLog to SimpleAuditLogReport --- docs/Admin/Get-SimpleAuditLogReport.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Admin/Get-SimpleAuditLogReport.md b/docs/Admin/Get-SimpleAuditLogReport.md index 022e112a5f..2c86d8db9d 100644 --- a/docs/Admin/Get-SimpleAuditLogReport.md +++ b/docs/Admin/Get-SimpleAuditLogReport.md @@ -1,13 +1,13 @@ --- -title: Get-SimpleAdminAuditLogReport.ps1 +title: Get-SimpleAuditLogReport.ps1 parent: Admin --- -## Get-SimpleAdminAuditLog +## Get-SimpleAuditLogReport -Download the latest release: [Get-SimpleAdminAuditLogReport.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-SimpleAdminAuditLogReport.ps1) +Download the latest release: [Get-SimpleAuditLogReport.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-SimpleAuditLogReport.ps1) -Exchange admin audit logs are not readily human readable. All of the data needed to understand what Cmdlet has been run is in the data but it is not very easy to read. Get-SimpleAdminAuditLog will take the results of an audit log search and provide a significantly more human readable version of the data. +Exchange admin audit logs are not readily human readable. All of the data needed to understand what Cmdlet has been run is in the data but it is not very easy to read. Get-SimpleAuditLogReport will take the results of an audit log search and provide a significantly more human readable version of the data. It will parse the audit log and attempt to reconstruct the actual Cmdlet that was run. From 4bf9a7bcd9c0cfc4bf889d18a67279eb78529a0a Mon Sep 17 00:00:00 2001 From: Bill Long Date: Fri, 6 Aug 2021 07:53:32 -0700 Subject: [PATCH 07/10] Find schema role owner using language-agnostic approach --- Security/src/Test-CVE-2021-34470.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/src/Test-CVE-2021-34470.ps1 b/Security/src/Test-CVE-2021-34470.ps1 index 2195b2bc62..dd1e7120ab 100644 --- a/Security/src/Test-CVE-2021-34470.ps1 +++ b/Security/src/Test-CVE-2021-34470.ps1 @@ -29,7 +29,7 @@ param ( $ErrorActionPreference = "Stop" -$schemaMaster = (netdom query fsmo | Select-String "Schema master\s+(\S+)").Matches.Groups[1].Value +$schemaMaster = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().SchemaRoleOwner $schemaDN = ([ADSI]"LDAP://$($schemaMaster)/RootDSE").schemaNamingContext From e782664f6d5b1032c49a9cae7eec93abc934c810 Mon Sep 17 00:00:00 2001 From: Bill Long Date: Fri, 6 Aug 2021 07:54:11 -0700 Subject: [PATCH 08/10] Remove language-specific portion of regex --- Security/src/Test-CVE-2021-34470.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/src/Test-CVE-2021-34470.ps1 b/Security/src/Test-CVE-2021-34470.ps1 index dd1e7120ab..a0b315fa14 100644 --- a/Security/src/Test-CVE-2021-34470.ps1 +++ b/Security/src/Test-CVE-2021-34470.ps1 @@ -65,7 +65,7 @@ if ($ApplyFix) { $storageGroupSchemaEntry.Properties["possSuperiors"] | Out-File $OutputFile -Append } - $isSchemaAdmin = $null -ne (whoami /groups | sls "\\Schema Admins\s+Group") + $isSchemaAdmin = $null -ne (whoami /groups | sls "\\Schema Admins\s+") if (-not $isSchemaAdmin) { Write-Warning "This user is not in Schema Admins. Cannot apply fix." return From 69ab89c17b49a9e7e62c39ca1a1ba1673f7a146d Mon Sep 17 00:00:00 2001 From: Bill Long Date: Fri, 6 Aug 2021 07:58:47 -0700 Subject: [PATCH 09/10] sls alias fails on 2008 R2 --- Security/src/Test-CVE-2021-34470.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/src/Test-CVE-2021-34470.ps1 b/Security/src/Test-CVE-2021-34470.ps1 index a0b315fa14..5e8f778566 100644 --- a/Security/src/Test-CVE-2021-34470.ps1 +++ b/Security/src/Test-CVE-2021-34470.ps1 @@ -65,7 +65,7 @@ if ($ApplyFix) { $storageGroupSchemaEntry.Properties["possSuperiors"] | Out-File $OutputFile -Append } - $isSchemaAdmin = $null -ne (whoami /groups | sls "\\Schema Admins\s+") + $isSchemaAdmin = $null -ne (whoami /groups | Select-String "\\Schema Admins\s+") if (-not $isSchemaAdmin) { Write-Warning "This user is not in Schema Admins. Cannot apply fix." return From 55faa8ca698048111d3ac9450c9d8666159ea4e7 Mon Sep 17 00:00:00 2001 From: Bill Long Date: Fri, 6 Aug 2021 08:12:33 -0700 Subject: [PATCH 10/10] Remove Schema Admin check --- Security/src/Test-CVE-2021-34470.ps1 | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Security/src/Test-CVE-2021-34470.ps1 b/Security/src/Test-CVE-2021-34470.ps1 index 5e8f778566..e6859d63f6 100644 --- a/Security/src/Test-CVE-2021-34470.ps1 +++ b/Security/src/Test-CVE-2021-34470.ps1 @@ -65,20 +65,18 @@ if ($ApplyFix) { $storageGroupSchemaEntry.Properties["possSuperiors"] | Out-File $OutputFile -Append } - $isSchemaAdmin = $null -ne (whoami /groups | Select-String "\\Schema Admins\s+") - if (-not $isSchemaAdmin) { - Write-Warning "This user is not in Schema Admins. Cannot apply fix." - return - } - - Write-Host "Attempting to apply fix..." + try { + Write-Host "Attempting to apply fix..." - $rootDSE = [ADSI]("LDAP://$($schemaMaster)/RootDSE") - [void]$rootDSE.Properties["schemaUpgradeInProgress"].Add(1) - $rootDSE.CommitChanges() + $rootDSE = [ADSI]("LDAP://$($schemaMaster)/RootDSE") + [void]$rootDSE.Properties["schemaUpgradeInProgress"].Add(1) + $rootDSE.CommitChanges() - $storageGroupSchemaEntry.Properties["possSuperiors"].Clear() - $storageGroupSchemaEntry.CommitChanges() + $storageGroupSchemaEntry.Properties["possSuperiors"].Clear() + $storageGroupSchemaEntry.CommitChanges() - Write-Host "Fix was applied successfully." + Write-Host "Fix was applied successfully." + } catch { + Write-Warning "Failed to apply fix. Please ensure you have Schema Admin rights. Error was: `n$_" + } }