PSWinReportingV2.psm1
function ConvertFrom-Color { [alias('Convert-FromColor')] [CmdletBinding()] param ([ValidateScript( { if ($($_ -in $Script:RGBColors.Keys -or $_ -match "^#([A-Fa-f0-9]{6})$" -or $_ -eq "") -eq $false) { throw "The Input value is not a valid colorname nor an valid color hex code." } else { $true } })] [alias('Colors')][string[]] $Color, [switch] $AsDecimal) $Colors = foreach ($C in $Color) { $Value = $Script:RGBColors."$C" if ($C -match "^#([A-Fa-f0-9]{6})$") { return $C } if ($null -eq $Value) { return } $HexValue = Convert-Color -RGB $Value Write-Verbose "Convert-FromColor - Color Name: $C Value: $Value HexValue: $HexValue" if ($AsDecimal) { [Convert]::ToInt64($HexValue, 16) } else { "#$($HexValue)" } } $Colors } function ConvertFrom-OperationType { param ([string] $OperationType) $Known = @{'%%14674' = 'Value Added' '%%14675' = 'Value Deleted' '%%14676' = 'Unknown' } foreach ($id in $OperationType) { if ($name = $Known[$id]) { return $name } } return $OperationType } function Convert-UAC { <# .SYNOPSIS Converts values from Events into proper format .DESCRIPTION Converts values from Events into proper format .PARAMETER UAC Parameter description .EXAMPLE Convert-UAC -UAC '%%1793' Convert-UAC -UAC '1793' Output: TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, RESERVED Convert-UAC -UAC '1793', '1794' Convert-UAC -UAC '121793' Output: PASSWD_CANT_CHANGE, ENCRYPTED_TEXT_PWD_ALLOWED, TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, INTERDOMAIN_TRUST_ACCOUNT, WORKSTATION_TRUST_ACCOUNT, RESERVED, RESERVED, DONT_EXPIRE_PASSWORD Convert-UAC -UAC 'C:\Onet33' Output: Same input as output Convert-UAC -UAC '121793' -OutputPerLine Output: One entry per line PASSWD_CANT_CHANGE ENCRYPTED_TEXT_PWD_ALLOWED TEMP_DUPLICATE_ACCOUNT NORMAL_ACCOUNT INTERDOMAIN_TRUST_ACCOUNT WORKSTATION_TRUST_ACCOUNT RESERVED RESERVED DONT_EXPIRE_PASSWORD .NOTES General notes #> [CmdletBinding()] param([string[]] $UAC, [string] $Separator) $Output = foreach ($String in $UAC) { $NumberAsString = $String.Replace('%', '') -as [int] if ($null -eq $NumberAsString) { return $UAC } $PropertyFlags = @("SCRIPT", "ACCOUNTDISABLE", "RESERVED", "HOMEDIR_REQUIRED", "LOCKOUT", "PASSWD_NOTREQD", "PASSWD_CANT_CHANGE", "ENCRYPTED_TEXT_PWD_ALLOWED", "TEMP_DUPLICATE_ACCOUNT", "NORMAL_ACCOUNT", "RESERVED", "INTERDOMAIN_TRUST_ACCOUNT", "WORKSTATION_TRUST_ACCOUNT", "SERVER_TRUST_ACCOUNT", "RESERVED", "RESERVED", "DONT_EXPIRE_PASSWORD", "MNS_LOGON_ACCOUNT", "SMARTCARD_REQUIRED", "TRUSTED_FOR_DELEGATION", "NOT_DELEGATED", "USE_DES_KEY_ONLY", "DONT_REQ_PREAUTH", "PASSWORD_EXPIRED", "TRUSTED_TO_AUTH_FOR_DELEGATION", "RESERVED", "PARTIAL_SECRETS_ACCOUNT" "RESERVED" "RESERVED" "RESERVED" "RESERVED" "RESERVED") 1..($PropertyFlags.Length) | Where-Object { $NumberAsString -bAnd [math]::Pow(2, $_) } | ForEach-Object { $PropertyFlags[$_] } } if ($Separator -eq '') { $Output } else { $Output -join $Separator } } function Find-DatesCurrentDayMinusDayX { param($days) $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(- $Days) $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddDays(- $Days).AddMilliseconds(-1) $DateParameters = @{DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesCurrentDayMinuxDaysX { param($days) $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(- $Days) $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1) $DateParameters = @{DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesCurrentHour { $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0) $DateTodayEnd = $DateTodayStart.AddHours(1) $DateParameters = @{DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesDayPrevious { $DateToday = (Get-Date).Date $DateYesterday = $DateToday.AddDays(-1) $DateParameters = @{DateFrom = $DateYesterday DateTo = $dateToday } return $DateParameters } function Find-DatesDayToday { $DateToday = (Get-Date).Date $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1) $DateParameters = @{DateFrom = $DateToday DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesMonthCurrent { $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1) $DateParameters = @{DateFrom = $DateMonthFirstDay DateTo = $DateMonthLastDay } return $DateParameters } function Find-DatesMonthPast { param([bool] $Force) $DateToday = (Get-Date).Date $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1) if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) { $DateParameters = @{DateFrom = $DateMonthPreviousFirstDay DateTo = $DateMonthFirstDay } return $DateParameters } else { return $null } } function Find-DatesPastHour { $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0 $DateTodayStart = $DateTodayEnd.AddHours(-1) $DateParameters = @{DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesPastWeek { param($DayName) $DateTodayStart = Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0 if ($DateTodayStart.DayOfWeek -ne $DayName) { return $null } $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7) $DateParameters = @{DateFrom = $DateTodayEnd DateTo = $DateTodayStart } return $DateParameters } function Find-DatesQuarterCurrent { param([bool] $Force) $Today = (Get-Date) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) $DateParameters = @{DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } function Find-DatesQuarterLast { param([bool] $Force) $Today = (Get-Date).AddDays(-90) $Yesterday = ((Get-Date).AddDays(-1)) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) { $DateParameters = @{DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } else { return $null } } function Format-AddSpaceToSentence { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Text Parameter description .EXAMPLE $test = @( 'OnceUponATime', 'OnceUponATime1', 'Money@Risk', 'OnceUponATime123', 'AHappyMan2014' 'OnceUponATime_123' ) Format-AddSpaceToSentence -Text $Test $Test | Format-AddSpaceToSentence -ToLowerCase .NOTES General notes #> [CmdletBinding()] param([Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text, [switch] $ToLowerCase) Begin { } Process { $Value = foreach ($T in $Text) { ($T -creplace '([A-Z\W_]|\d+)(?<![a-z])', ' $&').trim() } if ($ToLowerCase) { $Value.ToLower() } else { $Value } } End { } } function Format-FirstXChars { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Text Parameter description .PARAMETER NumberChars Parameter description .EXAMPLE Format-FirstChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15 .NOTES General notes #> param([string] $Text, [int] $NumberChars) return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join '' } function Get-FilesInFolder { [CmdletBinding()] param([string] $Folder, [string] $Extension = '*.evtx') $Files = Get-ChildItem -Path $Folder -Filter $Extension -Recurse $ReturnFiles = foreach ($File in $Files) { $File.FullName } return $ReturnFiles } function Get-Logger { [CmdletBinding(DefaultParameterSetName = "All")] param ([Parameter(Mandatory = $false, ParameterSetName = 'Logpath')][string] $LogPath, [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $LogsDir, [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $Filename, [switch] $ShowTime, [string] $TimeFormat = 'yyyy-MM-dd HH:mm:ss') if ($PSCmdlet.ParameterSetName -eq 'Complexpath') { if (-not $Filename) { $CallerName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path $MyInvocation.PSCommandPath -Leaf)) $Filename = "$([DateTime]::Now.ToString($TimeFormat) -replace('[^.\-\w]', '_'))_$CallerName.log" } $LogPath = Join-Path $LogsDir $Filename } if ($LogPath) { $LogsDir = [System.IO.Path]::GetDirectoryName($LogPath) New-Item $LogsDir -ItemType Directory -Force | Out-Null New-Item $LogPath -ItemType File -Force | Out-Null } $Logger = [PSCustomObject]@{LogPath = $LogPath ShowTime = $ShowTime TimeFormat = $TimeFormat } Add-Member -InputObject $Logger -MemberType ScriptMethod AddErrorRecord -Value { param([Parameter(Mandatory = $true)] [string]$String) if (-not $this.LogPath) { Write-Color -Text "[Error] ", $String -Color Red, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Error] ", $String -Color Red, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddInfoRecord -Value { param([Parameter(Mandatory = $true)] [string]$String) if (-not $this.LogPath) { Write-Color -Text "[Info] ", $String -Color Yellow, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Info] ", $String -Color Yellow, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddWarningRecord -Value { param([Parameter(Mandatory = $true)] [string]$String) if (-not $this.LogPath) { Write-Color -Text "[Warning] ", $String -Color Magenta, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Warning] ", $String -Color Magenta, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddRecord -Value { param([Parameter(Mandatory = $true)] [string]$String) if (-not $this.LogPath) { Write-Color -Text " $String" -Color White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text " $String" -Color White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddSuccessRecord -Value { param([Parameter(Mandatory = $true)] [string]$String) if (-not $this.LogPath) { Write-Color -Text "[Success] ", $String -Color Green, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Success] ", $String -Color Green, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } return $Logger } function Get-WinADForestControllers { [alias('Get-WinADDomainControllers')] <# .SYNOPSIS .DESCRIPTION Long description .PARAMETER TestAvailability Parameter description .EXAMPLE Get-WinADForestControllers -TestAvailability | Format-Table .EXAMPLE Get-WinADDomainControllers .EXAMPLE Get-WinADDomainControllers | Format-Table * Output: Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- ------- ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau... .NOTES General notes #> [CmdletBinding()] param([string[]] $Domain, [switch] $TestAvailability, [switch] $SkipEmpty) try { $Forest = Get-ADForest if (-not $Domain) { $Domain = $Forest.Domains } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage" return } $Servers = foreach ($D in $Domain) { try { $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop foreach ($S in $DC) { $Server = [ordered] @{Domain = $D HostName = $S.HostName Name = $S.Name Forest = $Forest.RootDomain IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly Site = $S.Site SchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') DomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') PDCEmulator = ($S.OperationMasterRoles -contains 'PDCEmulator') RIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') InfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') LdapPort = $S.LdapPort SslPort = $S.SslPort Pingable = $null Comment = '' } if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{Domain = $D HostName = '' Name = '' Forest = $Forest.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' Pingable = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } } if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } } return $Servers } 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) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } $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['QueryServers']['Forest'] = $DC $Findings.Domains = foreach ($_ in $ForestInformation.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)" continue } $Findings['QueryServers']["$Domain"] = $DC [Array] $AllDC = try { try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $DC.HostName[0] -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 -notin $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) { $Findings[$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { $Findings[$Domain] = $AllDC } $Findings[$Domain] } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } $Findings } function Remove-DuplicateObjects { <# .SYNOPSIS Short description .DESCRIPTION Long description .PARAMETER Object Parameter description .PARAMETER Property Parameter description .EXAMPLE $Array = @() $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} Write-Color 'Before' -Color Red $Array | Format-Table -A Write-Color 'After' -Color Green $Array = $Array | Sort-Object -Unique -Property 'Name', 'Val1','Val2' $Array | Format-Table -AutoSize .NOTES General notes #> [CmdletBinding()] param([System.Collections.IList] $Object, [string[]] $Property) if ($Object.Count -eq 0) { return $Object } else { return $Object | Sort-Object -Property $Property -Unique } } function Remove-WhiteSpace { param([string] $Text) $Text = $Text -replace '(^\s+|\s+$)', '' -replace '\s+', ' ' return $Text } function Save-XML { param ([string] $FilePath, [System.Xml.XmlNode] $xml) $utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) $writer = New-Object System.IO.StreamWriter($FilePath, $false, $utf8WithoutBom) $xml.Save($writer) $writer.Close() } function Send-Email { [CmdletBinding(SupportsShouldProcess = $true)] param ([alias('EmailParameters')][System.Collections.IDictionary] $Email, [string] $Body, [string[]] $Attachment, [System.Collections.IDictionary] $InlineAttachments, [string] $Subject, [string[]] $To, [PSCustomObject] $Logger) try { if ($Email.EmailTo) { $EmailParameters = $Email.Clone() } else { $EmailParameters = @{EmailFrom = $Email.From EmailTo = $Email.To EmailCC = $Email.CC EmailBCC = $Email.BCC EmailReplyTo = $Email.ReplyTo EmailServer = $Email.Server EmailServerPassword = $Email.Password EmailServerPasswordAsSecure = $Email.PasswordAsSecure EmailServerPasswordFromFile = $Email.PasswordFromFile EmailServerPort = $Email.Port EmailServerLogin = $Email.Login EmailServerEnableSSL = $Email.EnableSsl EmailEncoding = $Email.Encoding EmailEncodingSubject = $Email.EncodingSubject EmailEncodingBody = $Email.EncodingBody EmailSubject = $Email.Subject EmailPriority = $Email.Priority EmailDeliveryNotifications = $Email.DeliveryNotifications EmailUseDefaultCredentials = $Email.UseDefaultCredentials } } } catch { return @{Status = $False Error = $($_.Exception.Message) SentTo = '' } } $SmtpClient = [System.Net.Mail.SmtpClient]::new() if ($EmailParameters.EmailServer) { $SmtpClient.Host = $EmailParameters.EmailServer } else { return @{Status = $False Error = "Email Server Host is not set." SentTo = '' } } if ($EmailParameters.EmailServerPort) { $SmtpClient.Port = $EmailParameters.EmailServerPort } else { return @{Status = $False Error = "Email Server Port is not set." SentTo = '' } } if ($EmailParameters.EmailServerLogin) { $Credentials = Request-Credentials -UserName $EmailParameters.EmailServerLogin -Password $EmailParameters.EmailServerPassword -AsSecure:$EmailParameters.EmailServerPasswordAsSecure -FromFile:$EmailParameters.EmailServerPasswordFromFile -NetworkCredentials $SmtpClient.Credentials = $Credentials } if ($EmailParameters.EmailServerEnableSSL) { $SmtpClient.EnableSsl = $EmailParameters.EmailServerEnableSSL } $MailMessage = [System.Net.Mail.MailMessage]::new() $MailMessage.From = $EmailParameters.EmailFrom if ($To) { foreach ($T in $To) { $MailMessage.To.add($($T)) } } else { if ($EmailParameters.Emailto) { foreach ($To in $EmailParameters.Emailto) { $MailMessage.To.add($($To)) } } } if ($EmailParameters.EmailCC) { foreach ($CC in $EmailParameters.EmailCC) { $MailMessage.CC.add($($CC)) } } if ($EmailParameters.EmailBCC) { foreach ($BCC in $EmailParameters.EmailBCC) { $MailMessage.BCC.add($($BCC)) } } if ($EmailParameters.EmailReplyTo) { $MailMessage.ReplyTo = $EmailParameters.EmailReplyTo } $MailMessage.IsBodyHtml = $true if ($Subject -eq '') { $MailMessage.Subject = $EmailParameters.EmailSubject } else { $MailMessage.Subject = $Subject } $MailMessage.Priority = [System.Net.Mail.MailPriority]::$($EmailParameters.EmailPriority) if ($EmailParameters.EmailEncodingSubject) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingSubject) } else { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) } if ($EmailParameters.EmailEncodingBody) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingBody) } else { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) } if ($EmailParameters.EmailUseDefaultCredentials) { $SmtpClient.UseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials } if ($EmailParameters.EmailDeliveryNotifications) { $MailMessage.DeliveryNotificationOptions = $EmailParameters.EmailDeliveryNotifications } if ($PSBoundParameters.ContainsKey('InlineAttachments')) { $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, 'text/html') $MailMessage.AlternateViews.Add($BodyPart) foreach ($Entry in $InlineAttachments.GetEnumerator()) { try { $FilePath = $Entry.Value Write-Verbose $FilePath if ($Entry.Value.StartsWith('http')) { $FileName = $Entry.Value.Substring($Entry.Value.LastIndexOf("/") + 1) $FilePath = Join-Path $env:temp $FileName Invoke-WebRequest -Uri $Entry.Value -OutFile $FilePath } $ContentType = Get-MimeType -FileName $FilePath $InAttachment = [Net.Mail.LinkedResource]::new($FilePath, $ContentType) $InAttachment.ContentId = $Entry.Key $BodyPart.LinkedResources.Add($InAttachment) } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Error "Error inlining attachments: $ErrorMessage" } } } else { $MailMessage.Body = $Body } if ($PSBoundParameters.ContainsKey('Attachment')) { foreach ($Attach in $Attachment) { if (Test-Path -LiteralPath $Attach) { try { $File = [Net.Mail.Attachment]::new($Attach) $MailMessage.Attachments.Add($File) } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($Logger) { $Logger.AddErrorRecord("Error attaching file $Attach`: $ErrorMessage") } else { Write-Error "Error attaching file $Attach`: $ErrorMessage" } } } } } try { $MailSentTo = "$($MailMessage.To) $($MailMessage.CC) $($MailMessage.BCC)".Trim() if ($pscmdlet.ShouldProcess("$MailSentTo", "Send-Email")) { $SmtpClient.Send($MailMessage) $MailMessage.Dispose() return @{Status = $True Error = "" SentTo = $MailSentTo } } } catch { $MailMessage.Dispose() return @{Status = $False Error = $($_.Exception.Message) SentTo = "" } } } function Send-SqlInsert { [CmdletBinding()] param([Array] $Object, [System.Collections.IDictionary] $SqlSettings) if ($SqlSettings.SqlTableTranspose) { $Object = Format-TransposeTable -Object $Object } $SqlTable = Get-SqlQueryColumnInformation -SqlServer $SqlSettings.SqlServer -SqlDatabase $SqlSettings.SqlDatabase -Table $SqlSettings.SqlTable $PropertiesFromAllObject = Get-ObjectPropertiesAdvanced -Object $Object -AddProperties 'AddedWhen', 'AddedWho' $PropertiesFromTable = $SqlTable.Column_name if ($null -eq $SqlTable) { if ($SqlSettings.SqlTableCreate) { Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, table creation is allowed, mapping will be done either on properties from object or from TableMapping defined in config" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject $CreateTableSQL = New-SqlQueryCreateTable -SqlSettings $SqlSettings -TableMapping $TableMapping } else { Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, no table creation is allowed. Terminating" return "Error occured: SQL Table doesn't exists. SqlTableCreate option is disabled" } } else { if ($SqlSettings.SqlTableAlterIfNeeded) { if ($SqlSettings.SqlTableMapping) { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, but SqlTableMapping is already defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject } else { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, and SqlTableMapping is not defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject $AlterTableSQL = New-SqlQueryAlterTable -SqlSettings $SqlSettings -TableMapping $TableMapping -ExistingColumns $SqlTable.Column_name } } else { if ($SqlSettings.SqlTableMapping) { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is already defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject } else { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is not defined, using SqlTable Columns" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromTable -BasedOnSqlTable } } } $Queries = @(if ($CreateTableSQL) { foreach ($Sql in $CreateTableSQL) { $Sql } } if ($AlterTableSQL) { foreach ($Sql in $AlterTableSQL) { $Sql } } $SqlQueries = New-SqlQuery -Object $Object -SqlSettings $SqlSettings -TableMapping $TableMapping foreach ($Sql in $SqlQueries) { $Sql }) $ReturnData = foreach ($Query in $Queries) { try { if ($Query) { $Query Invoke-DbaQuery -SqlInstance "$($SqlSettings.SqlServer)" -Database "$($SqlSettings.SqlDatabase)" -Query $Query -ErrorAction Stop } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " "Error occured (Send-SqlInsert): $ErrorMessage" } } return $ReturnData } function Set-EmailBody { [CmdletBinding()] param([Object] $TableData, [alias('TableWelcomeMessage')][string] $TableMessageWelcome, [string] $TableMessageNoData = 'No changes happened during that period.') $Body = "<p><i><u>$TableMessageWelcome</u></i></p>" if ($($TableData | Measure-Object).Count -gt 0) { $Body += $TableData | ConvertTo-Html -Fragment | Out-String } else { $Body += "<p><i>$TableMessageNoData</i></p>" } return $body } function Set-EmailBodyPreparedTable { param($TableData, $TableWelcomeMessage) $body = "<p><i><u>$TableWelcomeMessage</u></i></p>" $body += $TableData return $body } function Set-EmailFormatting { [CmdletBinding()] param ($Template, [System.Collections.IDictionary] $FormattingParameters, [System.Collections.IDictionary] $ConfigurationParameters, [PSCustomObject] $Logger, [switch] $SkipNewLines, [string[]] $AddAfterOpening, [string[]] $AddBeforeClosing, [string] $Image) if ($ConfigurationParameters) { $WriteParameters = $ConfigurationParameters.DisplayConsole } else { $WriteParameters = @{ShowTime = $true; LogFile = ""; TimeFormat = "yyyy-MM-dd HH:mm:ss" } } if ($Image) { $Template = $Template -replace '<<Image>>', $Image } $Body = "<body>" if ($AddAfterOpening) { $Body += $AddAfterOpening } if (-not $SkipNewLines) { $Template = $Template.Split("`n") if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML <BR> tags...") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "<BR>", " tags." -Color White, Yellow, White, Yellow } foreach ($t in $Template) { $Body += "$t<br>" } } else { $Body += $Template } foreach ($style in $FormattingParameters.Styles.GetEnumerator()) { foreach ($value in $style.Value) { if ($value -eq "") { continue } if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($style.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($style.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow } $Body = $Body.Replace($value, "<$($style.Name)>$value</$($style.Name)>") } } foreach ($color in $FormattingParameters.Colors.GetEnumerator()) { foreach ($value in $color.Value) { if ($value -eq "") { continue } if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($color.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($color.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow } $Body = $Body.Replace($value, "<span style=color:$($color.Name)>$value</span>") } } foreach ($links in $FormattingParameters.Links.GetEnumerator()) { foreach ($link in $links.Value) { if ($link.Link -like "*@*") { if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding EMAIL Links for $($links.Key).") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " EMAIL ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White } $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='mailto:$($link.Link)?subject=$($Link.Subject)'>$($Link.Text)</a></span>" } else { if ($Logger) { $Logger.AddInfoRecord("[i] Preparing template - adding HTML Links for $($links.Key)") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White } $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='$($link.Link)'>$($Link.Text)</a></span>" } } } if ($AddAfterOpening) { $Body += $AddBeforeClosing } $Body += '</body>' if ($ConfigurationParameters) { if ($ConfigurationParameters.DisplayTemplateHTML -eq $true) { Get-HTML($Body) } } return $Body } function Set-EmailHead { param([System.Collections.IDictionary] $FormattingOptions) $head = @" <head> <style> BODY { background-color: white; font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } TABLE { border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse; font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } TH { border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #00297A; color: white; font-family: $($FormattingOptions.FontTableHeadingFamily); font-size: $($FormattingOptions.FontTableHeadingSize); } TR { font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } UL { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } LI { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } TD { border-width: 1px; padding-right: 2px; padding-left: 2px; padding-top: 0px; padding-bottom: 0px; border-style: solid; border-color: black; background-color: white; font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } H2 { font-family: $($FormattingOptions.FontHeadingFamily); font-size: $($FormattingOptions.FontHeadingSize); } P { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } </style> </head> "@ return $Head } function Set-EmailReportBranding { param([alias('FormattingOptions')] $FormattingParameters) if ($FormattingParameters.CompanyBranding.Link) { $Report = "<a style=`"text-decoration:none`" href=`"$($FormattingParameters.CompanyBranding.Link)`" class=`"clink logo-container`">" } else { $Report = '' } if ($FormattingParameters.CompanyBranding.Inline) { $Report += "<img width=<fix> height=<fix> src=`"cid:logo`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } else { $Report += "<img width=<fix> height=<fix> src=`"$($FormattingParameters.CompanyBranding.Logo)`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } if ($FormattingParameters.CompanyBranding.Width -ne "") { $Report = $Report -replace "width=<fix>", "width=$($FormattingParameters.CompanyBranding.Width)" } else { $Report = $Report -replace "width=<fix>", "" } if ($FormattingParameters.CompanyBranding.Height -ne "") { $Report = $Report -replace "height=<fix>", "height=$($FormattingParameters.CompanyBranding.Height)" } else { $Report = $Report -replace "height=<fix>", "" } return $Report } function Set-EmailWordReplacements { param($Body, $Replace, $ReplaceWith, [switch] $RegEx) if ($RegEx) { $Body = $Body -Replace $Replace, $ReplaceWith } else { $Body = $Body.Replace($Replace, $ReplaceWith) } return $Body } function Set-XML { param ([string] $FilePath, [string[]]$Paths, [string] $Node, [string] $Value) [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8 $XmlElement = $xmlDocument foreach ($Path in $Paths) { $XmlElement = $XmlElement.$Path } $XmlElement.$Node = $Value $xmlDocument.Save($FilePath) } function Split-Array { [CmdletBinding()] <# .SYNOPSIS Split an array .NOTES Version : July 2, 2017 - implemented suggestions from ShadowSHarmon for performance .PARAMETER inArray A one dimensional array you want to split .EXAMPLE This splits array into multiple arrays of 3 Example below wil return 1,2,3 + 4,5,6 + 7,8,9 Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -parts 3 .EXAMPLE This splits array into 3 parts regardless of amount of elements Split-array -inArray @(1,2,3,4,5,6,7,8,9,10) -size 3 # Link: https://gallery.technet.microsoft.com/scriptcenter/Split-an-array-into-parts-4357dcc1 #> param([Object] $inArray, [int]$parts, [int]$size) if ($inArray.Count -eq 1) { return $inArray } if ($parts) { $PartSize = [Math]::Ceiling($inArray.count / $parts) } if ($size) { $PartSize = $size $parts = [Math]::Ceiling($inArray.count / $size) } $outArray = New-Object 'System.Collections.Generic.List[psobject]' for ($i = 1; $i -le $parts; $i++) { $start = (($i - 1) * $PartSize) $end = (($i) * $PartSize) - 1 if ($end -ge $inArray.count) { $end = $inArray.count - 1 } $outArray.Add(@($inArray[$start..$end])) } return , $outArray } function Start-MyProgram { [CmdletBinding()] param ([string] $Program, [string[]] $CmdArgList, [System.Collections.IDictionary] $LoggerParameters) $Output = (cmd /c $Program $CmdArgList '2>&1') if (-not $LoggerParameters) { if ($Output) { return $Output } } else { $Logger = Get-Logger @LoggerParameters if ($null -ne $Output) { $Logger.AddInfoRecord("Running program $Program with output: $Output") } else { $Logger.AddInfoRecord("Running program $Program $CmdArgList") } } } 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. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. .DESCRIPTION Author: przemyslaw.klys at evotec.pl Project website: https://evotec.xyz/hub/scripts/write-color-ps1/ Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) .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 # Added in 0.5 Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow wc -t "my text" -c yellow -b green wc -text "my text" -c red .NOTES Additional Notes: - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx #> [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, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine) $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 } } if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewLine } } if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewLine } } if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))]" -NoNewline } if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { 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 } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } if ($Text.Count -and $LogFile) { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))]$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } } catch { $PSCmdlet.WriteError($_) } } } function Add-ToHashTable { param($Hashtable, $Key, $Value) if ($null -ne $Value -and $Value -ne '') { $Hashtable.Add($Key, $Value) } } function Compare-MultipleObjects { [CmdLetBinding()] param([System.Collections.IList] $Objects, [switch] $CompareSorted, [switch] $FormatOutput, [switch] $FormatDifferences, [switch] $Summary, [string] $Splitter = ', ', [string[]] $Property, [string[]] $ExcludeProperty, [switch] $AllProperties, [switch] $SkipProperties, [int] $First, [int] $Last, [Array] $Replace) if ($null -eq $Objects -or $Objects.Count -eq 1) { Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($($Objects.Count))." return } function Compare-TwoArrays { [CmdLetBinding()] param([string] $FieldName, [Array] $Object1, [Array] $Object2, [Array] $Replace) $Result = [ordered] @{Status = $false Same = [System.Collections.Generic.List[string]]::new() Add = [System.Collections.Generic.List[string]]::new() Remove = [System.Collections.Generic.List[string]]::new() } if ($Replace) { foreach ($R in $Replace) { if (($($R.Keys[0]) -eq '') -or ($($R.Keys[0]) -eq $FieldName)) { if ($null -ne $Object1) { $Object1 = $Object1 -replace $($R.Values)[0], $($R.Values)[1] } if ($null -ne $Object2) { $Object2 = $Object2 -replace $($R.Values)[0], $($R.Values)[1] } } } } if ($null -eq $Object1 -and $null -eq $Object2) { $Result['Status'] = $true } elseif (($null -eq $Object1) -or ($null -eq $Object2)) { $Result['Status'] = $false foreach ($O in $Object1) { $Result['Add'].Add($O) } foreach ($O in $Object2) { $Result['Remove'].Add($O) } } else { $ComparedObject = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -IncludeEqual foreach ($_ in $ComparedObject) { if ($_.SideIndicator -eq '==') { $Result['Same'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '<=')) { $Result['Add'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '=>')) { $Result['Remove'].Add($_.InputObject) } } IF ($Result['Add'].Count -eq 0 -and $Result['Remove'].Count -eq 0) { $Result['Status'] = $true } else { $Result['Status'] = $false } } $Result } if ($First -or $Last) { [int] $TotalCount = $First + $Last if ($TotalCount -gt 1) { $Objects = $Objects | Select-Object -First $First -Last $Last } else { Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($TotalCount)." return } } $ReturnValues = @($FirstElement = [ordered] @{ } $FirstElement['Name'] = 'Properties' if ($Summary) { $FirstElement['Same'] = $null $FirstElement['Different'] = $null } $FirstElement['Status'] = $false $FirstObjectProperties = Select-Properties -Objects $Objects -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties if (-not $SkipProperties) { if ($FormatOutput) { $FirstElement["Source"] = $FirstObjectProperties -join $Splitter } else { $FirstElement["Source"] = $FirstObjectProperties } [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) { if ($Objects[0] -is [System.Collections.IDictionary]) { [string[]] $CompareObjectProperties = $Objects[$i].Keys } else { [string[]] $CompareObjectProperties = $Objects[$i].PSObject.Properties.Name [string[]] $CompareObjectProperties = Select-Properties -Objects $Objects[$i] -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties } if ($FormatOutput) { $FirstElement["$i"] = $CompareObjectProperties -join $Splitter } else { $FirstElement["$i"] = $CompareObjectProperties } if ($CompareSorted) { $Value1 = $FirstObjectProperties | Sort-Object $Value2 = $CompareObjectProperties | Sort-Object } else { $Value1 = $FirstObjectProperties $Value2 = $CompareObjectProperties } $Status = Compare-TwoArrays -FieldName 'Properties' -Object1 $Value1 -Object2 $Value2 -Replace $Replace if ($FormatDifferences) { $FirstElement["$i-Add"] = $Status['Add'] -join $Splitter $FirstElement["$i-Remove"] = $Status['Remove'] -join $Splitter $FirstElement["$i-Same"] = $Status['Same'] -join $Splitter } else { $FirstElement["$i-Add"] = $Status['Add'] $FirstElement["$i-Remove"] = $Status['Remove'] $FirstElement["$i-Same"] = $Status['Same'] } $Status } if ($IsSame.Status -notcontains $false) { $FirstElement['Status'] = $true } else { $FirstElement['Status'] = $false } if ($Summary) { [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split') if ($FormatDifferences) { $FirstElement['Same'] = ($Collection[0] | ForEach-Object { $_ + 1 }) -join $Splitter $FirstElement['Different'] = ($Collection[1] | ForEach-Object { $_ + 1 }) -join $Splitter } else { $FirstElement['Same'] = $Collection[0] | ForEach-Object { $_ + 1 } $FirstElement['Different'] = $Collection[1] | ForEach-Object { $_ + 1 } } } [PSCustomObject] $FirstElement } foreach ($_ in $FirstObjectProperties) { $EveryOtherElement = [ordered] @{ } $EveryOtherElement['Name'] = $_ if ($Summary) { $EveryOtherElement['Same'] = $null $EveryOtherElement['Different'] = $null } $EveryOtherElement.Status = $false if ($FormatOutput) { $EveryOtherElement['Source'] = $Objects[0].$_ -join $Splitter } else { $EveryOtherElement['Source'] = $Objects[0].$_ } [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) { if ($FormatOutput) { $EveryOtherElement["$i"] = $Objects[$i].$_ -join $Splitter } else { $EveryOtherElement["$i"] = $Objects[$i].$_ } if ($CompareSorted) { $Value1 = $Objects[0].$_ | Sort-Object $Value2 = $Objects[$i].$_ | Sort-Object } else { $Value1 = $Objects[0].$_ $Value2 = $Objects[$i].$_ } $Status = Compare-TwoArrays -FieldName $_ -Object1 $Value1 -Object2 $Value2 -Replace $Replace if ($FormatDifferences) { $EveryOtherElement["$i-Add"] = $Status['Add'] -join $Splitter $EveryOtherElement["$i-Remove"] = $Status['Remove'] -join $Splitter $EveryOtherElement["$i-Same"] = $Status['Same'] -join $Splitter } else { $EveryOtherElement["$i-Add"] = $Status['Add'] $EveryOtherElement["$i-Remove"] = $Status['Remove'] $EveryOtherElement["$i-Same"] = $Status['Same'] } $Status } if ($IsSame.Status -notcontains $false) { $EveryOtherElement['Status'] = $true } else { $EveryOtherElement['Status'] = $false } if ($Summary) { [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split') if ($FormatDifferences) { $EveryOtherElement['Same'] = ($Collection[0] | ForEach-Object { $_ + 1 }) -join $Splitter $EveryOtherElement['Different'] = ($Collection[1] | ForEach-Object { $_ + 1 }) -join $Splitter } else { $EveryOtherElement['Same'] = $Collection[0] | ForEach-Object { $_ + 1 } $EveryOtherElement['Different'] = $Collection[1] | ForEach-Object { $_ + 1 } } } [PSCuStomObject] $EveryOtherElement }) if ($ReturnValues.Count -eq 1) { return , $ReturnValues } else { return $ReturnValues } } function Convert-Color { <# .Synopsis This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX) .Description This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX). Use it to convert your colors and prepare your graphics and HTML web pages. .Parameter RBG Enter the Red Green Blue value comma separated. Red: 51 Green: 51 Blue: 204 for example needs to be entered as 51,51,204 .Parameter HEX Enter the Hex value to be converted. Do not use the '#' symbol. (Ex: 3333CC converts to Red: 51 Green: 51 Blue: 204) .Example .\convert-color -hex FFFFFF Converts hex value FFFFFF to RGB .Example .\convert-color -RGB 123,200,255 Converts Red = 123 Green = 200 Blue = 255 to Hex value #> param([Parameter(ParameterSetName = "RGB", Position = 0)] [ValidateScript( { $_ -match '^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$' })] $RGB, [Parameter(ParameterSetName = "HEX", Position = 0)] [ValidateScript( { $_ -match '[A-Fa-f0-9]{6}' })] [string] $HEX) switch ($PsCmdlet.ParameterSetName) { "RGB" { if ($null -eq $RGB[2]) { Write-Error "Value missing. Please enter all three values seperated by comma." } $red = [convert]::Tostring($RGB[0], 16) $green = [convert]::Tostring($RGB[1], 16) $blue = [convert]::Tostring($RGB[2], 16) if ($red.Length -eq 1) { $red = '0' + $red } if ($green.Length -eq 1) { $green = '0' + $green } if ($blue.Length -eq 1) { $blue = '0' + $blue } Write-Output $red$green$blue } "HEX" { $red = $HEX.Remove(2, 4) $Green = $HEX.Remove(4, 2) $Green = $Green.remove(0, 2) $Blue = $hex.Remove(0, 4) $Red = [convert]::ToInt32($red, 16) $Green = [convert]::ToInt32($green, 16) $Blue = [convert]::ToInt32($blue, 16) Write-Output $red, $Green, $blue } } } function ConvertTo-OperatingSystem { [CmdletBinding()] param([string] $OperatingSystem, [string] $OperatingSystemVersion) if ($OperatingSystem -like '*Windows 10*') { $Systems = @{'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 (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.18898' = 'Windows 10 Insider Preview' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } elseif ($OperatingSystem -like '*Windows Server*') { $Systems = @{'5.2 (3790)' = 'Windows Server 2003' '6.1 (7601)' = 'Windows Server 2008 R2' '10.0 (18362)' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0 (17763)' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0 (17134)' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0 (14393)' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" '10.0.18362' = "Windows Server, version 1903 (Semi-Annual Channel) 1903" '10.0.17763' = "Windows Server 2019 (Long-Term Servicing Channel) 1809" '10.0.17134' = "Windows Server, version 1803 (Semi-Annual Channel) 1803" '10.0.14393' = "Windows Server 2016 (Long-Term Servicing Channel) 1607" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystem } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function Format-TransposeTable { [CmdletBinding()] param ([Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][System.Collections.ICollection] $Object, [ValidateSet("ASC", "DESC", "NONE")][String] $Sort = 'NONE') begin { $i = 0 } process { foreach ($myObject in $Object) { if ($myObject -is [System.Collections.IDictionary]) { $output = New-Object -TypeName PsObject Add-Member -InputObject $output -MemberType ScriptMethod -Name AddNote -Value { Add-Member -InputObject $this -MemberType NoteProperty -Name $args[0] -Value $args[1] } if ($Sort -eq 'ASC') { $myObject.Keys | Sort-Object -Descending:$false | ForEach-Object { $output.AddNote($_, $myObject.$_) } } elseif ($Sort -eq 'DESC') { $myObject.Keys | Sort-Object -Descending:$true | ForEach-Object { $output.AddNote($_, $myObject.$_) } } else { $myObject.Keys | ForEach-Object { $output.AddNote($_, $myObject.$_) } } $output } else { $output = [ordered] @{ } $myObject | Get-Member -MemberType *Property | ForEach-Object { $output.($_.name) = $myObject.($_.name) } $output } $i += 1 } } } function Get-HTML { [CmdletBinding()] param ($text) $text = $text.Split("`r") foreach ($t in $text) { Write-Host $t } } function Get-MimeType { [CmdletBinding()] param ([Parameter(Mandatory = $true)] [string] $FileName) $MimeMappings = @{'.jpeg' = 'image/jpeg' '.jpg' = 'image/jpeg' '.png' = 'image/png' } $Extension = [System.IO.Path]::GetExtension($FileName) $ContentType = $MimeMappings[ $Extension ] if ([string]::IsNullOrEmpty($ContentType)) { return New-Object System.Net.Mime.ContentType } else { return New-Object System.Net.Mime.ContentType($ContentType) } } function Get-ObjectPropertiesAdvanced { [CmdletBinding()] param ([object] $Object, [string[]] $AddProperties, [switch] $Sort) $Data = @{ } $Properties = New-ArrayList $HighestCount = 0 foreach ($O in $Object) { $ObjectProperties = $O.PSObject.Properties.Name $Count = $ObjectProperties.Count if ($Count -gt $HighestCount) { $Data.HighestCount = $Count $Data.HighestObject = $O $HighestCount = $Count } foreach ($Property in $ObjectProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique } } foreach ($Property in $AddProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique } $Data.Properties = if ($Sort) { $Properties | Sort-Object } else { $Properties } return $Data } function Get-SqlQueryColumnInformation { [CmdletBinding()] param ([string] $SqlServer, [string] $SqlDatabase, [string] $Table) $Table = $Table.Replace("dbo.", '').Replace('[', '').Replace(']', '') $SqlDatabase = $SqlDatabase.Replace('[', '').Replace(']', '') $SqlDatabase = "[$SqlDatabase]" $Query = "SELECT * FROM $SqlDatabase.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$Table'" $SqlReturn = @(try { Invoke-DbaQuery -ErrorAction Stop -SqlInstance $SqlServer -Query $Query } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " "Error occured (Get-SqlQueryColumnInformation): $ErrorMessage" }) return $SQLReturn } function New-Runspace { [cmdletbinding()] param ([int] $minRunspaces = 1, [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1) $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces) $RunspacePool.Open() return $RunspacePool } function New-SqlQuery { [CmdletBinding()] param ([Object] $SqlSettings, [Object] $Object, [Object] $TableMapping) $ArraySQLQueries = New-ArrayList if ($null -ne $Object) { foreach ($O in $Object) { $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList $ArrayValues = New-ArrayList if (-not $O.AddedWhen) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWhen" -Value (Get-Date) -Force } if (-not $O.AddedWho) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWho" -Value ($Env:USERNAME) -Force } $DuplicateString = [System.Text.StringBuilder]::new() foreach ($E in $O.PSObject.Properties) { $FieldName = $E.Name $FieldValue = $E.Value foreach ($MapKey in $TableMapping.Keys) { if ($FieldName -eq $MapKey) { $MapValue = $TableMapping.$MapKey $MapValueSplit = $MapValue -Split ',' if ($FieldValue -is [DateTime]) { $FieldValue = Get-Date $FieldValue -Format "yyyy-MM-dd HH:mm:ss" } if ($FieldValue -like "*'*") { $FieldValue = $FieldValue -Replace "'", "''" } Add-ToArray -List $ArrayKeys -Element "[$($MapValueSplit[0])]" if ([string]::IsNullOrWhiteSpace($FieldValue)) { Add-ToArray -List $ArrayValues -Element "NULL" } else { foreach ($ColumnName in $SqlSettings.SqlCheckBeforeInsert) { $DuplicateColumn = $ColumnName.Replace("[", '').Replace("]", '') if ($MapValueSplit[0] -eq $DuplicateColumn) { if ($DuplicateString.Length -ne 0) { $null = $DuplicateString.Append(" AND ") } $null = $DuplicateString.Append("[$DuplicateColumn] = '$FieldValue'") } } Add-ToArray -List $ArrayValues -Element "'$FieldValue'" } } } } if ($ArrayKeys) { if ($null -ne $SqlSettings.SqlCheckBeforeInsert -and $DuplicateString.Length -gt 0) { Add-ToArray -List $ArrayMain -Element "IF NOT EXISTS (" Add-ToArray -List $ArrayMain -Element "SELECT 1 FROM " Add-ToArray -List $ArrayMain -Element "$($SqlSettings.SqlTable) " Add-ToArray -List $ArrayMain -Element "WHERE $($DuplicateString.ToString())" Add-ToArray -List $ArrayMain -Element ")" } Add-ToArray -List $ArrayMain -Element "BEGIN" Add-ToArray -List $ArrayMain -Element "INSERT INTO $($SqlSettings.SqlTable) (" Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ') VALUES (' Add-ToArray -List $ArrayMain -Element ($ArrayValues -join ',') Add-ToArray -List $ArrayMain -Element ')' Add-ToArray -List $ArrayMain -Element "END" Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } } } return $ArraySQLQueries } function New-SqlQueryAlterTable { [CmdletBinding()] param ([Object]$SqlSettings, [Object]$TableMapping, [string[]] $ExistingColumns) $ArraySQLQueries = New-ArrayList $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList foreach ($MapKey in $TableMapping.Keys) { $MapValue = $TableMapping.$MapKey $Field = $MapValue -Split ',' if ($ExistingColumns -notcontains $MapKey -and $ExistingColumns -notcontains $Field[0]) { if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" } } } if ($ArrayKeys) { Add-ToArray -List $ArrayMain -Element "ALTER TABLE $($SqlSettings.SqlTable) ADD" Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ';' Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } return $ArraySQLQueries } function New-SqlQueryCreateTable { [CmdletBinding()] param ([Object]$SqlSettings, [Object]$TableMapping) $ArraySQLQueries = New-ArrayList $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList foreach ($MapKey in $TableMapping.Keys) { $MapValue = $TableMapping.$MapKey $Field = $MapValue -Split ',' if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" } } if ($ArrayKeys) { Add-ToArray -List $ArrayMain -Element "CREATE TABLE $($SqlSettings.SqlTable) (" Add-ToArray -List $ArrayMain -Element "ID int IDENTITY(1,1) PRIMARY KEY," Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ')' Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } return $ArraySQLQueries } function New-SqlTableMapping { [CmdletBinding()] param([Object] $SqlTableMapping, [Object] $Object, $Properties, [switch] $BasedOnSqlTable) if ($SqlTableMapping) { $TableMapping = $SqlTableMapping } else { $TableMapping = @{ } if ($BasedOnSqlTable) { foreach ($Property in $Properties) { $FieldName = $Property $FieldNameSql = $Property $TableMapping.$FieldName = $FieldNameSQL } } else { foreach ($O in $Properties.HighestObject) { foreach ($Property in $Properties.Properties) { $FieldName = $Property $FieldValue = $O.$Property $FieldNameSQL = $FieldName.Replace(' ', '') if ($FieldValue -is [DateTime]) { $TableMapping.$FieldName = "$FieldNameSQL,[datetime],null" } elseif ($FieldValue -is [int] -or $FieldValue -is [Int64]) { $TableMapping.$FieldName = "$FieldNameSQL,[bigint]" } elseif ($FieldValue -is [bool]) { $TableMapping.$FieldName = "$FieldNameSQL,[bit]" } else { $TableMapping.$FieldName = "$FieldNameSQL" } } } } } return $TableMapping } function Request-Credentials { [CmdletBinding()] param([string] $UserName, [string] $Password, [switch] $AsSecure, [switch] $FromFile, [switch] $Output, [switch] $NetworkCredentials, [string] $Service) if ($FromFile) { if (($Password -ne '') -and (Test-Path $Password)) { Write-Verbose "Request-Credentials - Reading password from file $Password" $Password = Get-Content -Path $Password } else { if ($Output) { return @{Status = $false; Output = $Service; Extended = 'File with password unreadable.' } } else { Write-Warning "Request-Credentials - Secure password from file couldn't be read. File not readable. Terminating." return } } } if ($AsSecure) { try { $NewPassword = $Password | ConvertTo-SecureString -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($ErrorMessage -like '*Key not valid for use in specified state*') { if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else { Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer." return } } else { if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else { Write-Warning -Message "Request-Credentials - $ErrorMessage" return } } } } else { $NewPassword = $Password } if ($UserName -and $NewPassword) { if ($AsSecure) { $Credentials = New-Object System.Management.Automation.PSCredential($Username, $NewPassword) } else { Try { $SecurePassword = $Password | ConvertTo-SecureString -asPlainText -Force -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($ErrorMessage -like '*Key not valid for use in specified state*') { if ($Output) { return @{Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else { Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer." return } } else { if ($Output) { return @{Status = $false; Output = $Service; Extended = $ErrorMessage } } else { Write-Warning -Message "Request-Credentials - $ErrorMessage" return } } } $Credentials = New-Object System.Management.Automation.PSCredential($Username, $SecurePassword) } } else { if ($Output) { return @{Status = $false; Output = $Service; Extended = 'Username or/and Password is empty' } } else { Write-Warning -Message 'Request-Credentials - UserName or Password are empty.' return } } if ($NetworkCredentials) { return $Credentials.GetNetworkCredential() } else { return $Credentials } } function Select-Properties { [CmdLetBinding()] param([Array] $Objects, [string[]] $Property, [string[]] $ExcludeProperty, [switch] $AllProperties) function Select-Unique { [CmdLetBinding()] param([System.Collections.IList] $Object) $New = $Object.ToLower() | Select-Object -Unique $Selected = foreach ($_ in $New) { $Index = $Object.ToLower().IndexOf($_) if ($Index -ne -1) { $Object[$Index] } } $Selected } if ($Objects.Count -eq 0) { Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.' return } if ($Objects[0] -is [System.Collections.IDictionary]) { if ($AllProperties) { [Array] $All = foreach ($_ in $Objects) { $_.Keys } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $Objects[0].Keys } if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) { $_ continue } } } elseif ($Property.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_) { $_ continue } } } elseif ($ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($ExcludeProperty -notcontains $_) { $_ continue } } } } else { if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty } if ($AllProperties) { [Array] $All = foreach ($_ in $Objects) { $_.PSObject.Properties.Name } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $Objects[0].PSObject.Properties.Name } } return $FirstObjectProperties } function Start-Runspace { [cmdletbinding()] param ([ScriptBlock] $ScriptBlock, [System.Collections.IDictionary] $Parameters, [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool) if ($ScriptBlock -ne '') { $runspace = [PowerShell]::Create() $null = $runspace.AddScript($ScriptBlock) if ($null -ne $Parameters) { $null = $runspace.AddParameters($Parameters) } $runspace.RunspacePool = $RunspacePool [PSCustomObject]@{Pipe = $runspace Status = $runspace.BeginInvoke() } } } function Stop-Runspace { [cmdletbinding()] param([Array] $Runspaces, [string] $FunctionName, [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool, [switch] $ExtendedOutput) [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) { foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) { $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) { Write-Error -ErrorRecord $e $e } foreach ($w in $($Runspace.Pipe.Streams.Warning)) { Write-Warning -Message $w } foreach ($v in $($Runspace.Pipe.Streams.Verbose)) { Write-Verbose -Message $v } if ($ExtendedOutput) { @{Output = $Runspace.Pipe.EndInvoke($Runspace.Status) Errors = $Errors } } else { $Runspace.Pipe.EndInvoke($Runspace.Status) } $Runspace.Status = $null } } $RunspacePool.Close() $RunspacePool.Dispose() if ($List.Count -eq 1) { return , $List } else { return $List } } 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 } function Add-ToArray { [CmdletBinding()] param([System.Collections.ArrayList] $List, [Object] $Element) [void] $List.Add($Element) } function Add-ToArrayAdvanced { [CmdletBinding()] param([System.Collections.ArrayList] $List, [Object] $Element, [switch] $SkipNull, [switch] $RequireUnique, [switch] $FullComparison, [switch] $Merge) if ($SkipNull -and $null -eq $Element) { return } if ($RequireUnique) { if ($FullComparison) { foreach ($ListElement in $List) { if ($ListElement -eq $Element) { $TypeLeft = Get-ObjectType -Object $ListElement $TypeRight = Get-ObjectType -Object $Element if ($TypeLeft.ObjectTypeName -eq $TypeRight.ObjectTypeName) { return } } } } else { if ($List -contains $Element) { return } } } if ($Merge) { [void] $List.AddRange($Element) } else { [void] $List.Add($Element) } } function Format-PSTable { [CmdletBinding()] param ([parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][System.Collections.ICollection] $Object, [switch] $SkipTitle, [string[]] $Property, [string[]] $ExcludeProperty, [Object] $OverwriteHeaders, [switch] $PreScanHeaders, [string] $Splitter = ';') if ($Object[0] -is [System.Collections.IDictionary]) { $Array = @(if (-not $SkipTitle) { , @('Name', 'Value') } foreach ($O in $Object) { foreach ($Name in $O.Keys) { $Value = $O[$Name] if ($O[$Name].Count -gt 1) { $Value = $O[$Name] -join $Splitter } else { $Value = $O[$Name] } , @($Name, $Value) } }) if ($Array.Count -eq 1) { , $Array } else { $Array } } elseif ($Object[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { return $Object } else { if ($Property) { $Object = $Object | Select-Object -Property $Property } $Array = @(if ($PreScanHeaders) { $Titles = Get-ObjectProperties -Object $Object } elseif ($OverwriteHeaders) { $Titles = $OverwriteHeaders } else { $Titles = $Object[0].PSObject.Properties.Name } if (-not $SkipTitle) { , $Titles } foreach ($O in $Object) { $ArrayValues = foreach ($Name in $Titles) { $Value = $O."$Name" if ($Value.Count -gt 1) { $Value -join $Splitter } elseif ($Value.Count -eq 1) { if ($Value.Value) { $Value.Value } else { $Value } } else { '' } } , $ArrayValues }) if ($Array.Count -eq 1) { , $Array } else { $Array } } } 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 "$($([System.IO.Path]::GetTempFileName()).Replace('.tmp','')).$Extension" } if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" } } function New-ArrayList { [CmdletBinding()] param() $List = [System.Collections.ArrayList]::new() return , $List } function Remove-EmptyValues { [CmdletBinding()] param([System.Collections.IDictionary] $Hashtable, [switch] $Recursive, [int] $Rerun) foreach ($_ in [string[]] $Hashtable.Keys) { if ($Recursive) { if ($Hashtable[$_] -is [System.Collections.IDictionary]) { if ($Hashtable[$_].Count -eq 0) { $Hashtable.Remove($_) } else { Remove-EmptyValues -Hashtable $Hashtable[$_] -Recursive:$Recursive } } else { if ($null -eq $Hashtable[$_]) { $Hashtable.Remove($_) } elseif ($Hashtable[$_] -is [string] -and $Hashtable[$_] -eq '') { $Hashtable.Remove($_) } } } else { if ($null -eq $Hashtable[$_]) { $Hashtable.Remove($_) } elseif ($Hashtable[$_] -is [string] -and $Hashtable[$_] -eq '') { $Hashtable.Remove($_) } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValues -Hashtable $Hashtable -Recursive:$Recursive } } } function Get-ObjectProperties { [CmdletBinding()] param ([System.Collections.ICollection] $Object, [string[]] $AddProperties, [switch] $Sort, [bool] $RequireUnique = $true) $Properties = @(foreach ($O in $Object) { $ObjectProperties = $O.PSObject.Properties.Name $ObjectProperties } foreach ($Property in $AddProperties) { $Property }) if ($Sort) { return $Properties | Sort-Object -Unique:$RequireUnique } else { return $Properties | Select-Object -Unique:$RequireUnique } } function Get-ObjectType { [CmdletBinding()] param([Object] $Object, [string] $ObjectName = 'Random Object Name', [switch] $VerboseOnly) $ReturnData = [ordered] @{ } $ReturnData.ObjectName = $ObjectName if ($Object -ne $null) { try { $TypeInformation = $Object.GetType() $ReturnData.ObjectTypeName = $TypeInformation.Name $ReturnData.ObjectTypeBaseName = $TypeInformation.BaseType $ReturnData.SystemType = $TypeInformation.UnderlyingSystemType } catch { $ReturnData.ObjectTypeName = '' $ReturnData.ObjectTypeBaseName = '' $ReturnData.SystemType = '' } try { $TypeInformationInsider = $Object[0].GetType() $ReturnData.ObjectTypeInsiderName = $TypeInformationInsider.Name $ReturnData.ObjectTypeInsiderBaseName = $TypeInformationInsider.BaseType $ReturnData.SystemTypeInsider = $TypeInformationInsider.UnderlyingSystemType } catch { $ReturnData.ObjectTypeInsiderName = '' $ReturnData.ObjectTypeInsiderBaseName = '' $ReturnData.SystemTypeInsider = '' } } else { $ReturnData.ObjectTypeName = '' $ReturnData.ObjectTypeBaseName = '' $ReturnData.SystemType = '' $ReturnData.ObjectTypeInsiderName = '' $ReturnData.ObjectTypeInsiderBaseName = '' $ReturnData.SystemTypeInsider = '' } Write-Verbose "Get-ObjectType - ObjectTypeName: $($ReturnData.ObjectTypeName)" Write-Verbose "Get-ObjectType - ObjectTypeBaseName: $($ReturnData.ObjectTypeBaseName)" Write-Verbose "Get-ObjectType - SystemType: $($ReturnData.SystemType)" Write-Verbose "Get-ObjectType - ObjectTypeInsiderName: $($ReturnData.ObjectTypeInsiderName)" Write-Verbose "Get-ObjectType - ObjectTypeInsiderBaseName: $($ReturnData.ObjectTypeInsiderBaseName)" Write-Verbose "Get-ObjectType - SystemTypeInsider: $($ReturnData.SystemTypeInsider)" if ($VerboseOnly) { return } else { return Format-TransposeTable -Object $ReturnData } } function Get-RandomStringName { [cmdletbinding()] param([int] $Size = 31, [switch] $ToLower, [switch] $ToUpper, [switch] $LettersOnly) [string] $MyValue = @(if ($LettersOnly) { ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ })) } else { ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ })) }) if ($ToLower) { return $MyValue.ToLower() } if ($ToUpper) { return $MyValue.ToUpper() } return $MyValue } $LdapBindingsDetails = @{Enabled = $false Events = @{Enabled = $true Events = 2889 LogName = 'Directory Service' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'Date' = 'When' 'NoNameA0' = 'Ip/Port' 'NoNameA1' = 'Account Name' 'NoNameA2' = 'Bind Type' 'LevelDisplayName' = 'Level' 'TaskDisplayName' = 'Task' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } Overwrite = [ordered] @{"Bind Type#1" = "Bind Type", 0, "Unsigned" "Bind Type#2" = "Bind Type", 1, "Simple" } SortBy = 'When' } } $LdapBindingsSummary = @{Enabled = $false Events = @{Enabled = $true Events = 2887 LogName = 'Directory Service' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'NoNameA0' = 'Number of simple binds performed without SSL/TLS' 'NoNameA1' = 'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing' 'Date' = 'When' 'LevelDisplayName' = 'Level' 'TaskDisplayName' = 'Task' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } function Find-EventsTo { [CmdletBinding()] param ([Array] $Events, [alias('IgnoreWords', 'PriorityWords')][System.Collections.IDictionary] $DataSet, [switch] $Ignore, [switch] $Prioritize) if ($DataSet.Count -eq 0) { return $Events } $EventsToReturn = foreach ($Event in $Events) { $Found = $false foreach ($Set in $DataSet.GetEnumerator()) { if ($Set.Value -ne '') { foreach ($Value in $Set.Value) { $ColumnName = $Set.Name if ($Event.$ColumnName -like $Value) { $Found = $true } } } } if ($Ignore) { if (-not $Found) { $Event } } if ($Prioritize) { if ($Found) { $Event } } } return $EventsToReturn } function Get-EventsOutput { [CmdletBinding()] param([System.Collections.IDictionary] $Definitions, [Array] $AllEvents, [switch] $Quiet) $Results = @{ } foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) { if ($Definitions.$Report.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Running $Report") } $TimeExecution = Start-TimeLog $Results.$Report = foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { if ($Definitions.$Report.$SubReport.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Running $Report with subsection $SubReport") } [string] $EventsType = $Definitions.$Report.$SubReport.LogName [Array] $EventsNeeded = $Definitions.$Report.$SubReport.Events [Array] $EventsFound = Get-EventsTranslation -Events $AllEvents -EventsDefinition $Definitions.$Report.$SubReport -EventIDs $EventsNeeded -EventsType $EventsType if (-not $Quiet) { $Logger.AddInfoRecord("Ending $Report with subsection $SubReport events found $($EventsFound.Count)") } $EventsFound } } $ElapsedTimeReport = Stop-TimeLog -Time $TimeExecution -Option OneLiner if (-not $Quiet) { $Logger.AddInfoRecord("Ending $Report - Time to run $ElapsedTimeReport") } } } return $Results } function Get-EventsTranslation { [CmdletBinding()] param([Array] $Events, [System.Collections.IDictionary] $EventsDefinition, [Array] $EventIDs, [string] $EventsType) if ($EventsDefinition.Filter.Count -gt 0) { foreach ($Entry in $EventsDefinition.Filter.Keys) { $EveryFilter = $EventsDefinition.Filter[$Entry] $StrippedFilter = $Entry -replace '#[0-9]{1,2}', '' [Array] $Splitter = $StrippedFilter.Split('#') if ($Splitter.Count -gt 1) { $PropertyName = $Splitter[0] $Operator = $Splitter[1] } else { $PropertyName = $StrippedFilter $Operator = 'eq' } $Events = foreach ($V in $EveryFilter) { foreach ($_ in $Events) { if ($Operator -eq 'eq') { if ($_.$PropertyName -eq $V) { $_ } } elseif ($Operator -eq 'like') { if ($_.$PropertyName -like $V) { $_ } } elseif ($Operator -eq 'ne') { if ($_.$PropertyName -ne $V) { $_ } } elseif ($Operator -eq 'gt') { if ($_.$PropertyName -gt $V) { $_ } } elseif ($Operator -eq 'lt') { if ($_.$PropertyName -lt $V) { $_ } } } } } } if ($EventsDefinition.FilterOr.Count -gt 0) { $Events = foreach ($_ in $Events) { foreach ($Entry in $EventsDefinition.FilterOr.Keys) { $StrippedFilter = $Entry -replace '#[0-9]{1,2}', '' [Array] $Splitter = $StrippedFilter.Split('#') if ($Splitter.Count -gt 1) { $PropertyName = $Splitter[0] $Operator = $Splitter[1] } else { $PropertyName = $StrippedFilter $Operator = 'eq' } $Value = $EventsDefinition.FilterOr[$Entry] foreach ($V in $Value) { if ($Operator -eq 'eq') { if ($_.$PropertyName -eq $V) { $_ } } elseif ($Operator -eq 'like') { if ($_.$PropertyName -like $V) { $_ } } elseif ($Operator -eq 'ne') { if ($_.$PropertyName -ne $V) { $_ } } elseif ($Operator -eq 'gt') { if ($_.$PropertyName -gt $V) { $_ } } elseif ($Operator -eq 'lt') { if ($_.$PropertyName -lt $V) { $_ } } } } } } $MyValue = foreach ($Event in $Events) { if (($Event.LogName -eq $EventsType) -and ($Event.ID -in $EventIDs)) { } else { continue } $HashTable = [ordered] @{ } foreach ($EventProperty in $Event.PSObject.Properties) { if ($null -ne $EventsDefinition.Ignore) { if ($EventsDefinition.Ignore.Contains($EventProperty.Name)) { if ($EventProperty.Value -like $EventsDefinition.Ignore[$EventProperty.Name]) { continue } } } if ($null -ne $EventsDefinition.Functions) { if ($EventsDefinition.Functions.Contains($EventProperty.Name)) { if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Remove-WhiteSpace') { $EventProperty.Value = Remove-WhiteSpace -Text $EventProperty.Value } if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Split-OnSpace') { $EventProperty.Value = $EventProperty.Value -Split ' ' } if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Convert-UAC') { $EventProperty.Value = Convert-UAC -UAC $EventProperty.Value -Separator ', ' } if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'ConvertFrom-OperationType') { $EventProperty.Value = ConvertFrom-OperationType -OperationType $EventProperty.Value } if ($EventsDefinition.Functions[$EventProperty.Name] -contains 'Clean-IpAddress') { $EventProperty.Value = if ($EventProperty.Value -match "::1") { 'localhost' } else { $EventProperty.Value } } } } if ($null -ne $EventsDefinition.Fields -and $EventsDefinition.Fields.Contains($EventProperty.Name)) { $HashTable[$EventsDefinition.Fields[$EventProperty.Name]] = $EventProperty.Value } else { $HashTable[$EventProperty.Name] = $EventProperty.Value } } if ($null -ne $EventsDefinition.Overwrite) { foreach ($Entry in $EventsDefinition.Overwrite.Keys) { [Array] $OverwriteObject = $EventsDefinition.Overwrite.$Entry $StrippedFilter = $Entry -replace '#[0-9]{1,2}', '' [Array] $Splitter = $StrippedFilter.Split('#') if ($Splitter.Count -gt 1) { $PropertyName = $Splitter[0] $Operator = $Splitter[1] } else { $PropertyName = $StrippedFilter $Operator = 'eq' } if ($OverwriteObject.Count -eq 3) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $OverwriteObject[2] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 4) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $OverwriteObject[2] } else { $HashTable[$PropertyName] = $OverwriteObject[3] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Couint -eq 1) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[0])] } } } if ($null -ne $EventsDefinition.OverwriteByField) { foreach ($Entry in $EventsDefinition.OverwriteByField.Keys) { [Array] $OverwriteObject = $EventsDefinition.OverwriteByField.$Entry $StrippedFilter = $Entry -replace '#[0-9]{1,2}', '' [Array] $Splitter = $StrippedFilter.Split('#') if ($Splitter.Count -gt 1) { $PropertyName = $Splitter[0] $Operator = $Splitter[1] } else { $PropertyName = $StrippedFilter $Operator = 'eq' } if ($OverwriteObject.Count -eq 3) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } } elseif ($Operator -eq 'ne') { if ($HashTable[($OverwriteObject[0])] -ne $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 4) { if ($Operator -eq 'eq') { if ($HashTable[($OverwriteObject[0])] -eq $OverwriteObject[1]) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[2])] } else { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[3])] } } elseif ($Operator -eq 'ne') { } elseif ($Operator -eq 'like') { } elseif ($Operator -eq 'gt') { } elseif ($Operator -eq 'lt') { } } elseif ($OverwriteObject.Count -eq 1) { $HashTable[$PropertyName] = $HashTable[($OverwriteObject[0])] } } } [PsCustomObject]$HashTable } $MyValue = Find-EventsTo -Ignore -Events $MyValue -DataSet $EventsDefinition.IgnoreWords if ($null -eq $EventsDefinition.Fields) { return $MyValue | Sort-Object $EventsDefinition.SortBy } else { return $MyValue | Select-Object @($EventsDefinition.Fields.Values) | Sort-Object $EventsDefinition.SortBy } } function Export-ReportToCSV { [CmdletBinding()] param ([bool] $Report, [System.Collections.IDictionary] $ReportOptions, [string] $Extension, [string] $ReportName, [Array] $ReportTable) if ($Report) { $ReportFilePath = Set-ReportFileName -ReportOptions $ReportOptions -ReportExtension $Extension -ReportName $ReportName if ($ReportTable.Count -gt 0) { $ReportTable | Export-Csv -Encoding Unicode -Path $ReportFilePath } return $ReportFilePath } else { return '' } } function Export-ReportToHTML { param ($Report, $ReportTable, $ReportTableText, [switch] $Special) if ($Report -eq $true) { if ($special) { return Set-EmailBodyPreparedTable -TableData $ReportTable -TableWelcomeMessage $ReportTableText } return Set-EmailBody -TableData $ReportTable -TableWelcomeMessage $ReportTableText } else { return '' } } function Export-ReportToSQL { [CmdletBinding()] param ([System.Collections.IDictionary] $Report, [System.Collections.IDictionary] $ReportOptions, [string] $ReportName, [Array] $ReportTable) if ($Report.Enabled) { if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Use) { if ($Report.Contains('EnabledSqlGlobal') -and $Report.EnabledSqlGlobal) { $Logger.AddInfoRecord("Sending $ReportName to SQL at Global level") $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $ReportOptions.AsSql -Verbose:$ReportOptions.Debug.Verbose foreach ($Query in $SqlQuery) { $Logger.AddInfoRecord("MS SQL Output: $Query") } } } if ($Report.Contains('ExportToSql') -and $Report.ExportToSql.Use) { $Logger.AddInfoRecord("Sending $ReportName to SQL at Local level") $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $Report.ExportToSql -Verbose:$ReportOptions.Debug.Verbose foreach ($Query in $SqlQuery) { $Logger.AddInfoRecord("MS SQL Output: $Query") } } } } function Export-ReportToXLSX { [CmdletBinding()] param([bool] $Report, [System.Collections.IDictionary] $ReportOptions, [string] $ReportFilePath, [string] $ReportName, [Array] $ReportTable) if (($Report -eq $true) -and ($($ReportTable | Measure-Object).Count -gt 0)) { $ReportTable | ConvertTo-Excel -Path $ReportFilePath -WorkSheetname $ReportName -AutoSize -FreezeTopRow -AutoFilter } } function Export-ToCSV { [CmdletBinding()] param ([bool] $Report, [string] $Path, [string] $FilePattern, [string] $DateFormat, [string] $ReportName, [Array] $ReportTable) if ($Report) { $ReportFileName = Set-ReportFile -Path $Path -FileNamePattern $FilePattern -DateFormat $DateFormat -ReportName $ReportName try { if ($ReportTable.Count -gt 0) { $ReportTable | Export-Csv -Encoding Unicode -LiteralPath $ReportFileName -ErrorAction Stop -Force } return $ReportFileName } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error saving file $ReportFileName.") $Logger.AddErrorRecord("Error: $ErrorMessage") return '' } } else { return '' } } function Export-ToSQL { [CmdletBinding()] param ([System.Collections.IDictionary] $Report, [System.Collections.IDictionary] $ReportOptions, [string] $ReportName, [Array] $ReportTable) if ($Report.Enabled) { if ($ReportOptions.Contains('AsSql') -and $ReportOptions.AsSql.Enabled -and $Report.Contains('SqlExport') -and $Report.SqlExport.EnabledGlobal) { $Logger.AddInfoRecord("Sending $ReportName to SQL at Global level") $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $ReportOptions.AsSql -Verbose:$ReportOptions.Debug.Verbose foreach ($Query in $SqlQuery) { $Logger.AddInfoRecord("MS SQL GLOBAL Output: $Query") } } if ($Report.Contains('SqlExport') -and $Report.SqlExport.Enabled) { $Logger.AddInfoRecord("Sending $ReportName to SQL at Local level") $SqlQuery = Send-SqlInsert -Object $ReportTable -SqlSettings $Report.SqlExport -Verbose:$ReportOptions.Debug.Verbose foreach ($Query in $SqlQuery) { $Logger.AddInfoRecord("MS SQL LOCAL Output: $Query") } } } } function Get-NotificationParameters { [CmdletBinding()] param([System.Collections.IDictionary] $Notifications, [string] $ActivityTitle, [string] $Priority, [string] $Type) $Object = @{Uri = '' ActivityImageLink = '' Color = '' AvatarImage = '' AvatarName = '' } if ($null -ne $Notifications.$Priority) { $Logger.AddInfoRecord("Service $Type is using $Priority priority Event on $ActivityTitle") $Option = $Priority } else { $Logger.AddInfoRecord("Service $Type is using Default priority Event on $ActivityTitle") $Option = 'Default' } $Object.Uri = $Notifications[$Option].Uri $Object.AvatarName = $Notifications[$Option].AvatarName $Object.AvatarImage = $Notifications[$Option].AvatarImage $Object.ActivityImageLink = $Notifications[$Option].ActivityLinks.Default.Link $Object.Color = $Notifications[$Option].ActivityLinks.Default.Color foreach ($Type in $Notifications[$option].ActivityLinks.Keys | Where-Object { $_ -ne 'Default' }) { if ($ActivityTitle -like "*$Type*") { $Object.ActivityImageLink = $Notifications[$Option].ActivityLinks.$Type.Link $Object.Color = $Notifications[$Option].ActivityLinks.$Type.Color break } } return $Object } function Send-Notificaton { [CmdletBinding()] param([PSCustomObject] $Events, [Parameter(Mandatory = $true)][alias('ReportOptions')][System.Collections.IDictionary] $Options, [string] $Priority = 'Default') Begin { } Process { if ($Events -ne $null) { foreach ($Event in $Events) { [string] $MessageTitle = 'Active Directory Changes' [string] $ActivityTitle = $($Event.Action).Trim() $Teams = Get-NotificationParameters -Type 'Microsoft Teams' -Notifications $Options.Notifications.MicrosoftTeams -ActivityTitle $ActivityTitle -Priority $Priority $Slack = Get-NotificationParameters -Type 'Slack' -Notifications $Options.Notifications.Slack -ActivityTitle $ActivityTitle -Priority $Priority $Discord = Get-NotificationParameters -Type 'Discord' -Notifications $Options.Notifications.Discord -ActivityTitle $ActivityTitle -Priority $Priority $FactsSlack = @() $FactsTeams = @() $FactsDiscord = @() foreach ($Property in $event.PSObject.Properties) { if ($null -ne $Property.Value -and $Property.Value -ne '') { if ($Property.Name -eq 'When') { $FactsTeams += New-TeamsFact -Name $Property.Name -Value $Property.Value.DateTime $FactsSlack += @{title = $Property.Name; value = $Property.Value.DateTime; short = $true } $FactsDiscord += New-DiscordFact -Name $Property.Name -Value $Property.Value.DateTime -Inline $true } else { $FactsTeams += New-TeamsFact -Name $Property.Name -Value $Property.Value $FactsSlack += @{title = $Property.Name; value = $Property.Value; short = $true } $FactsDiscord += New-DiscordFact -Name $Property.Name -Value $Property.Value -Inline $true } } } if ($Options.Notifications.Slack.Enabled) { $SlackChannel = $Options.Notifications.Slack.$Priority.Channel $SlackColor = ConvertFrom-Color -Color $Slack.Color $Data = New-SlackMessageAttachment -Color $SlackColor -Title "$MessageTitle - $ActivityTitle" -Fields $FactsSlack -Fallback $ActivityTitle | New-SlackMessage -Channel $SlackChannel -IconEmoji :bomb: | Send-SlackMessage -Uri $Slack.Uri -Verbose Write-Color @script:WriteParameters -Text "[i] Slack output: ", $Data -Color White, Yellow } if ($Options.Notifications.MicrosoftTeams.Enabled) { $Section1 = New-TeamsSection -ActivityTitle $ActivityTitle -ActivityImageLink $Teams.ActivityImageLink -ActivityDetails $FactsTeams $Data = Send-TeamsMessage -URI $Teams.Uri -MessageTitle $MessageTitle -Color $Teams.Color -Sections $Section1 -Supress $false -MessageSummary $ActivityTitle Write-Color @script:WriteParameters -Text "[i] Teams output: ", $Data -Color White, Yellow } if ($Options.Notifications.Discord.Enabled) { $Thumbnail = New-DiscordImage -Url $Discord.ActivityImageLink $Section1 = New-DiscordSection -Title $ActivityTitle -Facts $FactsDiscord -Thumbnail $Thumbnail -Color $Discord.Color -Verbose $Data = Send-DiscordMessage -WebHookUrl $Discord.Uri -Sections $Section1 -AvatarName $Discord.AvatarName -AvatarUrl $Discord.AvatarUrl -OutputJSON Write-Color @script:WriteParameters -Text "[i] Discord output: ", $Data -Color White, Yellow } if ($Options.Notifications.MSSQL.Enabled) { $SqlQuery = Send-SqlInsert -Object $Event -SqlSettings $Options.Notifications.MSSQL -Verbose:$Options.Debug.Verbose foreach ($Query in $SqlQuery) { Write-Color @script:WriteParameters -Text '[i] ', 'MS SQL Output: ', $Query -Color White, White, Yellow } } if ($Options.Notifications.Email.Enabled) { if ($Options.Notifications.Email.AsHTML.Enabled) { $Logger.AddInfoRecord('Prepare email head and body') $HtmlHead = Set-EmailHead -FormattingOptions $Options.Notifications.Email.AsHTML.Formatting $HtmlBody = Set-EmailReportBranding -FormattingParameters $Options.Notifications.Email.AsHTML.Formatting $HtmlBody += Export-ReportToHTML -Report $true -ReportTable $Event -ReportTableText "Quick notification event" $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $Options.Notifications.Email.AsHTML.Formatting -ConfigurationParameters $Options -Logger $Logger -SkipNewLines $HTML = $HtmlHead + $HtmlBody $EmailBody = $HTML $ReportHTMLPath = Set-ReportFile -Path $Env:TEMP -FileNamePattern 'PSWinReporting.html' -DateFormat $null try { $HTML | Out-File -Encoding Unicode -FilePath $ReportHTMLPath -ErrorAction Stop $Logger.AddInfoRecord("Saving report to file: $ReportHTMLPath") if ($Options.SendMail.Attach.HTML) { $AttachHTML += $ReportHTMLPath $AttachedReports += $ReportHTMLPath } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.") $Logger.AddErrorRecord("Error: $ErrorMessage") } $TemporarySubject = $Options.Notifications.Email.$Priority.Parameters.Subject $Logger.AddInfoRecord('Sending email with reports') if ($Options.Notifications.Email.AsHTML.Formatting.CompanyBranding.Inline) { $SendMail = Send-Email -EmailParameters $Options.Notifications.Email.$Priority.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -InlineAttachments @{logo = $Options.Notifications.Email.AsHTML.Formatting.CompanyBranding.Logo } -Logger $Logger } else { $SendMail = Send-Email -EmailParameters $Options.Notifications.Email.$Priority.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -Logger $Logger } if ($SendMail.Status) { $Logger.AddInfoRecord('Email successfully sent') } else { $Logger.AddInfoRecord("Error sending message: $($SendMail.Error)") } Remove-ReportsFiles -KeepReports $false -ReportFiles $ReportHTMLPath } } } } } End { } } function Start-ReportSpecial { [CmdletBinding()] param ([System.Collections.IDictionary] $Dates, [alias('ReportOptions')][System.Collections.IDictionary] $Options, [alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions, [alias('Servers', 'Computers')][System.Collections.IDictionary] $Target) $Verbose = ($PSCmdlet.MyInvocation.BoundParameters['Verbose'] -eq $true) $Time = Start-TimeLog $AttachedReports = @() $AttachXLSX = @() $AttachHTML = @() $AttachDynamicHTML = @() $AttachCSV = @() [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target foreach ($Entry in $ExtendedInput) { if ($Entry.Type -eq 'Computer') { $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } else { $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } [Array] $AllEvents = Get-Events -ExtendedInput $ExtendedInput -ErrorAction SilentlyContinue -ErrorVariable AllErrors -Verbose:$Verbose $Logger.AddInfoRecord("Found $($AllEvents.Count) events.") foreach ($Errors in $AllErrors) { $Logger.AddErrorRecord($Errors) } if ($Options.RemoveDuplicates.Enabled) { $Logger.AddInfoRecord("Removing Duplicates from all events. Current list contains $($AllEvents.Count) events") $AllEvents = Remove-DuplicateObjects -Object $AllEvents -Property $Options.RemoveDuplicates.Properties $Logger.AddInfoRecord("Removed Duplicates - following $($AllEvents.Count) events will be analyzed further") } $Results = Get-EventsOutput -Definitions $Definitions -AllEvents $AllEvents if ($Options.AsHTML.Enabled) { $Logger.AddInfoRecord('Prepare email head and body') $HtmlHead = Set-EmailHead -FormattingOptions $Options.AsHTML.Formatting $HtmlBody = Set-EmailReportBranding -FormattingParameters $Options.AsHTML.Formatting $HtmlBody += Set-EmailReportDetails -FormattingParameters $Options.AsHTML.Formatting -Dates $Dates -Warnings $Warnings foreach ($ReportName in $Definitions.Keys) { $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName -ToLowerCase $HtmlBody += Export-ReportToHTML -Report $Definitions.$ReportName.Enabled -ReportTable $Results.$ReportName -ReportTableText "Following $ReportNameTitle happened" } $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateDays**' -ReplaceWith $time.Elapsed.Days $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateHours**' -ReplaceWith $time.Elapsed.Hours $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMinutes**' -ReplaceWith $time.Elapsed.Minutes $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateSeconds**' -ReplaceWith $time.Elapsed.Seconds $HtmlBody = Set-EmailWordReplacements -Body $HtmlBody -Replace '**TimeToGenerateMilliseconds**' -ReplaceWith $time.Elapsed.Milliseconds $HtmlBody = Set-EmailFormatting -Template $HtmlBody -FormattingParameters $Options.AsHTML.Formatting -ConfigurationParameters $Options -Logger $Logger -SkipNewLines $HTML = $HtmlHead + $HtmlBody $ReportHTMLPath = Set-ReportFile -Path $Options.AsHTML.Path -FileNamePattern $Options.AsHTML.FilePattern -DateFormat $Options.AsHTML.DateFormat try { $HTML | Out-File -Encoding Unicode -FilePath $ReportHTMLPath -ErrorAction Stop $Logger.AddInfoRecord("Saving report to file: $ReportHTMLPath") if ($Options.SendMail.Attach.HTML) { $AttachHTML += $ReportHTMLPath $AttachedReports += $ReportHTMLPath } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.") $Logger.AddErrorRecord("Error: $ErrorMessage") } } if ($Options.AsDynamicHTML.Enabled) { $DynamicHTMLPath = Set-ReportFile -Path $Options.AsDynamicHTML.Path -FileNamePattern $Options.AsDynamicHTML.FilePattern -DateFormat $Options.AsDynamicHTML.DateFormat $null = New-HTML -TitleText $Options.AsDynamicHTML.Title -UseCssLinks:$Options.AsDynamicHTML.EmbedCSS -UseJavaScriptLinks:$Options.AsDynamicHTML.EmbedJS { foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName if ($Definitions.$ReportName.Enabled) { New-HTMLSection -HeaderText $ReportNameTitle -CanCollapse { New-HTMLPanel { if ($null -ne $Results.$ReportName) { New-HTMLTable -DataTable $Results.$ReportName -HideFooter } } } } } } -FilePath $DynamicHTMLPath try { if ($Options.SendMail.Attach.DynamicHTML) { $AttachDynamicHTML += $DynamicHTMLPath $AttachedReports += $DynamicHTMLPath } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error saving file $ReportHTMLPath.") $Logger.AddErrorRecord("Error: $ErrorMessage") } } if ($Options.AsExcel.Enabled) { $Logger.AddInfoRecord('Prepare Microsoft Excel (.XLSX) file with Events') $ReportFilePathXLSX = Set-ReportFile -Path $Options.AsExcel.Path -FileNamePattern $Options.AsExcel.FilePattern -DateFormat $Options.AsExcel.DateFormat foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName Export-ReportToXLSX -Report $Definitions.$ReportName.Enabled -ReportOptions $Options -ReportFilePath $ReportFilePathXLSX -ReportName $ReportNameTitle -ReportTable $Results.$ReportName } if ($Options.SendMail.Attach.XLSX) { $AttachXLSX += $ReportFilePathXLSX $AttachedReports += $ReportFilePathXLSX } } if ($Options.AsCSV.Enabled) { $ReportFilePathCSV = @() $Logger.AddInfoRecord('Prepare CSV files with Events') foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { $ReportFilePathCSV += Export-ToCSV -Report $Definitions.$ReportName.Enabled -ReportName $ReportName -ReportTable $Results.$ReportName -Path $Options.AsCSV.Path -FilePattern $Options.AsCSV.FilePattern -DateFormat $Options.AsCSV.DateFormat } if ($Options.SendMail.Attach.CSV) { $AttachCSV += $ReportFilePathCSV $AttachedReports += $ReportFilePathCSV } } if ($Options.AsHTML.Enabled -and $Options.AsHTML.OpenAsFile) { try { if ($ReportHTMLPath -ne '' -and (Test-Path -LiteralPath $ReportHTMLPath)) { Invoke-Item -LiteralPath $ReportHTMLPath } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error opening file $ReportHTMLPath.") } } if ($Options.AsDynamicHTML.Enabled -and $Options.AsDynamicHTML.OpenAsFile) { try { if ($DynamicHTMLPath -ne '' -and (Test-Path -LiteralPath $DynamicHTMLPath)) { Invoke-Item -LiteralPath $DynamicHTMLPath } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error opening file $DynamicHTMLPath.") } } if ($Options.AsExcel.Enabled -and $Options.AsExcel.OpenAsFile) { try { if ($ReportFilePathXLSX -ne '' -and (Test-Path -LiteralPath $ReportFilePathXLSX)) { Invoke-Item -LiteralPath $ReportFilePathXLSX } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error opening file $ReportFilePathXLSX.") } } if ($Options.AsCSV.Enabled -and $Options.AsCSV.OpenAsFile) { foreach ($CSV in $AttachCSV) { try { if ($CSV -ne '' -and (Test-Path -LiteralPath $CSV)) { Invoke-Item -LiteralPath $CSV } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Logger.AddErrorRecord("Error opening file $CSV.") } } } $AttachedReports = $AttachedReports | Sort-Object -Unique if ($Options.SendMail.Enabled) { foreach ($Report in $AttachedReports) { $Logger.AddInfoRecord("Following files will be attached to email $Report") } if ($Options.SendMail.InlineHTML) { $EmailBody = $HTML } else { $EmailBody = '' } $TemporarySubject = $Options.SendMail.Parameters.Subject -replace "<<DateFrom>>", "$($Dates.DateFrom)" -replace "<<DateTo>>", "$($Dates.DateTo)" $Logger.AddInfoRecord('Sending email with reports') if ($Options.AsHTML.Formatting.CompanyBranding.Inline) { $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -InlineAttachments @{logo = $Options.AsHTML.Formatting.CompanyBranding.Logo } -Logger $Logger } else { $SendMail = Send-Email -EmailParameters $Options.SendMail.Parameters -Body $EmailBody -Attachment $AttachedReports -Subject $TemporarySubject -Logger $Logger } if ($SendMail.Status) { $Logger.AddInfoRecord('Email successfully sent') } else { $Logger.AddErrorRecord("Error sending message: $($SendMail.Error)") } Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.XLSX -ReportFiles $AttachXLSX Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.CSV -ReportFiles $AttachCSV Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.HTML -ReportFiles $AttachHTML Remove-ReportsFiles -KeepReports $Options.SendMail.KeepReports.DynamicHTML -ReportFiles $AttachDynamicHTML } foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { $ReportNameTitle = Format-AddSpaceToSentence -Text $ReportName Export-ToSql -Report $Definitions.$ReportName -ReportOptions $Options -ReportName $ReportNameTitle -ReportTable $Results.$ReportName } $ElapsedTime = Stop-TimeLog -Time $Time $Logger.AddInfoRecord("Time to finish $ElapsedTime") } function Get-ChoosenDates { [CmdletBinding()] param([System.Collections.IDictionary] $ReportTimes) $Dates = @(if ($ReportTimes.Contains('PastHour') -and $ReportTimes.PastHour.Enabled) { $DatesPastHour = Find-DatesPastHour if ($DatesPastHour) { $DatesPastHour } } if ($ReportTimes.Contains('CurrentHour') -and $ReportTimes.CurrentHour.Enabled) { $DatesCurrentHour = Find-DatesCurrentHour if ($DatesCurrentHour) { $DatesCurrentHour } } if ($ReportTimes.Contains('PastDay') -and $ReportTimes.PastDay.Enabled) { $DatesDayPrevious = Find-DatesDayPrevious if ($DatesDayPrevious) { $DatesDayPrevious } } if ($ReportTimes.Contains('CurrentDay') -and $ReportTimes.CurrentDay.Enabled) { $DatesDayToday = Find-DatesDayToday if ($DatesDayToday) { $DatesDayToday } } if ($ReportTimes.Contains('OnDay') -and $ReportTimes.OnDay.Enabled) { foreach ($Day in $ReportTimes.OnDay.Days) { $DatesReportOnDay = Find-DatesPastWeek $Day if ($DatesReportOnDay) { $DatesReportOnDay } } } if ($ReportTimes.Contains('PastMonth') -and $ReportTimes.PastMonth.Enabled) { $DatesMonthPrevious = Find-DatesMonthPast -Force $ReportTimes.PastMonth.Force if ($DatesMonthPrevious) { $DatesMonthPrevious } } if ($ReportTimes.Contains('CurrentMonth') -and $ReportTimes.CurrentMonth.Enabled) { $DatesMonthCurrent = Find-DatesMonthCurrent if ($DatesMonthCurrent) { $DatesMonthCurrent } } if ($ReportTimes.Contains('PastQuarter') -and $ReportTimes.PastQuarter.Enabled) { $DatesQuarterLast = Find-DatesQuarterLast -Force $ReportTimes.PastQuarter.Force if ($DatesQuarterLast) { $DatesQuarterLast } } if ($ReportTimes.Contains('CurrentQuarter') -and $ReportTimes.CurrentQuarter.Enabled) { $DatesQuarterCurrent = Find-DatesQuarterCurrent if ($DatesQuarterCurrent) { $DatesQuarterCurrent } } if ($ReportTimes.Contains('CurrentDayMinusDayX') -and $ReportTimes.CurrentDayMinusDayX.Enabled) { $DatesCurrentDayMinusDayX = Find-DatesCurrentDayMinusDayX $ReportTimes.CurrentDayMinusDayX.Days if ($DatesCurrentDayMinusDayX) { $DatesCurrentDayMinusDayX } } if ($ReportTimes.Contains('CurrentDayMinuxDaysX') -and $ReportTimes.CurrentDayMinuxDaysX.Enabled) { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX $ReportTimes.CurrentDayMinuxDaysX.Days if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($ReportTimes.Contains('CustomDate') -and $ReportTimes.CustomDate.Enabled) { $DatesCustom = @{DateFrom = $ReportTimes.CustomDate.DateFrom DateTo = $ReportTimes.CustomDate.DateTo } if ($DatesCustom) { $DatesCustom } } if ($ReportTimes.Contains('Everything') -and $ReportTimes.Everything.Enabled) { $DatesEverything = @{DateFrom = Get-Date -Year 1600 -Month 1 -Day 1 DateTo = Get-Date -Year 2300 -Month 1 -Day 1 } $DatesEverything } if ($ReportTimes.Contains('Last3days') -and $ReportTimes.Last3days.Enabled) { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 3 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($ReportTimes.Contains('Last7days') -and $ReportTimes.Last7days.Enabled) { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 7 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } } if ($ReportTimes.Contains('Last14days') -and $ReportTimes.Last14days.Enabled) { $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 14 if ($DatesCurrentDayMinusDaysX) { $DatesCurrentDayMinusDaysX } }) return $Dates } function Get-DatesDefinitions { [CmdletBinding()] param([string[]] $Skip) $Times = foreach ($Key in $Script:ReportTimes.Keys) { if ($SkipTime -notcontains $Key) { $Key } } $Times } function Get-EventLogFileList { [CmdletBinding()] param([System.Collections.IDictionary] $Sections) $EventFiles = @(if ($Sections.Contains("Directories")) { foreach ($Folder in $Sections.Directories.Keys) { $Files = Get-FilesInFolder -Folder $Sections.Directories.$Folder -Extension '*.evtx' foreach ($File in $Files) { $File } } } if ($Sections.Contains("Files")) { foreach ($FileName in $Sections.Files.Keys) { $File = $($Sections.Files.$FileName) if ($File -and (Test-Path -LiteralPath $File)) { $File } else { if (-not $Quiet) { $Logger.AddErrorRecord("File $File doesn't exists. Skipping for scan.") } } } }) return $EventFiles | Sort-Object -Unique } function Get-EventsDefinitions { [CmdLetBinding()] param([System.Collections.IDictionary] $Definitions) [string] $ConfigurationPath = "$Env:ALLUSERSPROFILE\Evotec\PSWinReporting\Definitions" try { $Files = Get-ChildItem -LiteralPath $ConfigurationPath -Filter '*.xml' -ErrorAction Stop } catch { $Files = $null } $AllDefinitions = $Script:ReportDefinitions if ($null -ne $Files) { try { foreach ($File in $Files) { $AllDefinitions += Import-Clixml -LiteralPath $File.FullName } if ($Definitions) { $AllDefinitions += $Definitions } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($ErrorMessage -like '*Item has already been added. Key in dictionary*') { Write-Warning "Get-EventsDefinitions - Duplicate key in definition. Please make sure names in Hashtables are unique." } else { Write-Warning "Get-EventsDefinitions - Error: $ErrorMessage" } $AllDefinitions = $null } } return $AllDefinitions } function Get-ServersList { [CmdletBinding()] param([System.Collections.IDictionary] $Definitions, [System.Collections.IDictionary] $Target, [System.Collections.IDictionary] $Dates, [switch] $Quiet, [string] $Who, [string] $Whom, [string] $NotWho, [string] $NotWhom) $ServersList = @(if ($Target.Servers.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Preparing servers list - defined list") } [Array] $Servers = foreach ($Server in $Target.Servers.Keys | Where-Object { $_ -ne 'Enabled' }) { if ($Target.Servers.$Server -is [System.Collections.IDictionary]) { [ordered] @{ComputerName = $Target.Servers.$Server.ComputerName LogName = $Target.Servers.$Server.LogName } } elseif ($Target.Servers.$Server -is [Array] -or $Target.Servers.$Server -is [string]) { $Target.Servers.$Server } } $Servers } if ($Target.DomainControllers.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Preparing servers list - domain controllers autodetection") } [Array] $Servers = (Get-WinADDomainControllers -SkipEmpty).HostName $Servers }) if ($Target.LocalFiles.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Preparing file list - defined event log files") } $Files = Get-EventLogFileList -Sections $Target.LocalFiles } [Array] $LogNames = foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { if ($Definitions.$Report.Enabled) { foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled' }) { if ($Definitions.$Report.$SubReport.Enabled) { $Definitions.$Report.$SubReport.LogName } } } } if ($LogNames.Count -eq 0) { $Logger.AddErrorRecord("Definitions provided don't contain any enabled report or subevents within report. Please check your definitions and try again.") Exit } [Array] $ExtendedInput = foreach ($Log in $LogNames | Sort-Object -Unique) { $EventIDs = foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { if ($Definitions.$Report.Enabled) { foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled' }) { if ($Definitions.$Report.$SubReport.Enabled) { if ($Definitions.$Report.$SubReport.LogName -eq $Log) { $Definitions.$Report.$SubReport.Events } } } } } $NamedDataFilter = @{ } if ($Who -ne '') { $NamedDataFilter.SubjectUserName = $Who } if ($Whom -ne '') { $NamedDataFilter.TargetUserName = $Whom } $NamedDataExcludeFilter = @{ } if ($NotWho -ne '') { $NamedDataExcludeFilter.SubjectUserName = $NotWho } if ($NotWhom -ne '') { $NamedDataExcludeFilter.TargetUserName = $NotWhom } $OutputServers = foreach ($Server in $ServersList) { if ($Server -is [System.Collections.IDictionary]) { [PSCustomObject]@{Server = $Server.ComputerName LogName = $Server.LogName EventID = $EventIDs | Sort-Object -Unique Type = 'Computer' DateFrom = $Dates.DateFrom DateTo = $Dates.DateTo NamedDataFilter = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { } NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { } } } elseif ($Server -is [Array] -or $Server -is [string]) { foreach ($S in $Server) { [PSCustomObject]@{Server = $S LogName = $Log EventID = $EventIDs | Sort-Object -Unique Type = 'Computer' DateFrom = $Dates.DateFrom DateTo = $Dates.DateTo NamedDataFilter = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { } NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { } } } } } $OutputFiles = foreach ($File in $FIles) { [PSCustomObject]@{Server = $File LogName = $Log EventID = $EventIDs | Sort-Object -Unique Type = 'File' DateFrom = $Dates.DateFrom DateTo = $Dates.DateTo NamedDataFilter = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { } NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { } } } $OutputServers $OutputFiles } if ($ExtendedInput.Count -gt 1) { $ExtendedInput } else { , $ExtendedInput } } function Get-ServersListLimited { [CmdletBinding()] param([System.Collections.IDictionary] $Target, [int64] $RecordID, [switch] $Quiet, [string] $Who, [string] $Whom, [string] $NotWho, [string] $NotWhom) if ($Target.Servers.Enabled) { if (-not $Quiet) { $Logger.AddInfoRecord("Preparing servers list - defined list") } [Array] $Servers = foreach ($Server in $Target.Servers.Keys | Where-Object { $_ -ne 'Enabled' }) { if ($Target.Servers.$Server -is [System.Collections.IDictionary]) { [ordered] @{ComputerName = $Target.Servers.$Server.ComputerName LogName = $Target.Servers.$Server.LogName } } elseif ($Target.Servers.$Server -is [Array] -or $Target.Servers.$Server -is [string]) { $Target.Servers.$Server } } } [Array] $ExtendedInput = foreach ($Server in $Servers) { [PSCustomObject] @{Server = $Server.ComputerName LogName = $Server.LogName RecordID = $RecordID NamedDataFilter = if ($NamedDataFilter.Count -ne 0) { $NamedDataFilter } else { } NamedDataExcludeFilter = if ($NamedDataExcludeFilter.Count -ne 0) { $NamedDataExcludeFilter } else { } } } if ($ExtendedInput.Count -gt 1) { $ExtendedInput } else { , $ExtendedInput } } function Move-ArchivedLogs { [CmdletBinding()] param ([string] $ServerName, [string] $SourcePath, [string] $DestinationPath) $NewSourcePath = "\\$ServerName\$($SourcePath.Replace(':\','$\'))" $PathExists = Test-Path -LiteralPath $NewSourcePath if ($PathExists) { Write-Color @script:WriteParameters '[i] Moving log file from ', $NewSourcePath, ' to ', $DestinationPath -Color White, Yellow, White, Yellow Move-Item -Path $NewSourcePath -Destination $DestinationPath if (!$?) { Write-Color @script:WriteParameters '[i] File ', $NewSourcePath, ' couldn not be moved.' -Color White, Yellow, White } } else { Write-Color @script:WriteParameters '[i] Event Log Move ', $NewSourcePath, ' was skipped. No file exists on drive.' -Color White, Yellow, White, Yellow } } function New-TargetServers { [CmdLetBinding()] param([string[]] $Servers, [switch] $UseDC) $Target = [ordered]@{Servers = [ordered] @{Enabled = if ($Servers -and ($UseDC -eq $false)) { $true } else { $false } Servers = $Servers } DomainControllers = [ordered] @{Enabled = $UseDC } LocalFiles = [ordered] @{Enabled = $false Directories = [ordered] @{ } Files = [ordered] @{ } } } return $Target } function Protect-ArchivedLogs { [CmdletBinding()] param ($TableEventLogClearedLogs, [string] $DestinationPath) foreach ($BackupEvent in $TableEventLogClearedLogs) { if ($BackupEvent.'Event ID' -eq 1105) { $SourcePath = $BackupEvent.'Backup Path' $ServerName = $BackupEvent.'Domain Controller' if ($SourcePath -and $ServerName -and $DestinationPath) { Write-Color @script:WriteParameters '[i] Found Event Log file ', $SourcePath, ' on ', $ServerName, '. Will try moving to: ', $DestinationPath -Color White, Yellow, White, Yellow Move-ArchivedLogs -ServerName $ServerName -SourcePath $SourcePath -DestinationPath $DestinationPath } } } } function Remove-ReportsFiles { [CmdletBinding()] param([bool] $KeepReports, [Array] $ReportFiles) if (-not $KeepReports) { foreach ($Report in $ReportFiles) { if ($Report -ne '' -and (Test-Path -LiteralPath $Report)) { $Logger.AddInfoRecord("Removing file $Report") try { Remove-Item -LiteralPath $Report -ErrorAction Stop } catch { $Logger.AddErrorRecord("Error removing file: $($_.Exception.Message)") } } } } } function Remove-Subscription { [CmdletBinding()] param([switch] $All, [switch] $Own, [System.Collections.IDictionary] $LoggerParameters) $Subscriptions = Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'es' foreach ($Subscription in $Subscriptions) { if ($Own -eq $true -and $Subscription -like '*PSWinReporting*') { $Logger.AddInfoRecord("Deleting own providers - $Subscription") Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription -LoggerParameters $LoggerParameters } if ($All -eq $true -and $Subscription -notlike '*PSWinReporting*') { $Logger.AddInfoRecord("Deleting own providers - $Subscription") Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'ds', $Subscription -LoggerParameters $LoggerParameters } } } function Set-EmailReportDetails($FormattingParameters, $Dates, $Warnings) { $DateReport = Get-Date $Report = "<p style=`"background-color:white;font-family:$($FormattingParameters.FontFamily);font-size:$($FormattingParameters.FontSize)`">" + "<strong>Report Time:</strong> $DateReport <br>" + "<strong>Report Period:</strong> $($Dates.DateFrom) to $($Dates.DateTo) <br>" + "<strong>Account Executing Report :</strong> $env:userdomain\$($env:username.toupper()) on $($env:ComputerName.toUpper()) <br>" + "<strong>Time to generate:</strong> **TimeToGenerateDays** days, **TimeToGenerateHours** hours, **TimeToGenerateMinutes** minutes, **TimeToGenerateSeconds** seconds, **TimeToGenerateMilliseconds** milliseconds" if ($($Warnings | Measure-Object).Count -gt 0) { $Report += "<br><br><strong>Warnings:</strong>" foreach ($warning in $Warnings) { $Report += "<br> $warning" } } $Report += "</p>" return $Report } function Set-ReportFile { param([string] $Path, [alias('FilePattern')][string] $FileNamePattern, [string] $DateFormat, [string] $Extension, [string] $ReportName) $FileNamePattern = $FileNamePattern.Replace('<currentdate>', $(Get-Date -f $DateFormat)) $FileNamePattern = $FileNamePattern.Replace('<extension>', $Extension) $FileNamePattern = $FileNamePattern.Replace('<reportname>', $ReportName) return "$Path\$FileNamePattern" } function Set-ReportFileName { param([System.Collections.IDictionary] $ReportOptions, [string] $ReportExtension, [string] $ReportName = "") if ($ReportOptions.KeepReportsPath -ne "") { $Path = $ReportOptions.KeepReportsPath } else { $Path = $env:TEMP } $ReportPath = $Path + "\" + $ReportOptions.FilePattern $ReportPath = $ReportPath -replace "<currentdate>", $(Get-Date -f $ReportOptions.FilePatternDateFormat) if ($ReportName -ne "") { $ReportPath = $ReportPath.Replace(".<extension>", "-$ReportName.$ReportExtension") } else { $ReportPath = $ReportPath.Replace(".<extension>", ".$ReportExtension") } return $ReportPath } $Script:LoggerParameters = @{ShowTime = $false TimeFormat = 'yyyy-MM-dd HH:mm:ss' } $Script:ProgramWecutil = "wecutil.exe" $Script:ProgramWevtutil = 'wevtutil.exe' $Script:ReportDefinitions = [ordered] @{ADUserChanges = @{Enabled = $false SqlExport = @{EnabledGlobal = $false Enabled = $false SqlServer = 'EVO1' SqlDatabase = 'SSAE18' SqlTable = 'dbo.[EventsNewSpecial]' SqlTableCreate = $true SqlTableAlterIfNeeded = $false SqlCheckBeforeInsert = 'EventRecordID', 'DomainController' SqlTableMapping = [ordered] @{'Event ID' = 'EventID,[int]' 'Who' = 'EventWho' 'When' = 'EventWhen,[datetime]' 'Record ID' = 'EventRecordID,[bigint]' 'Domain Controller' = 'DomainController' 'Action' = 'Action' 'Group Name' = 'GroupName' 'User Affected' = 'UserAffected' 'Member Name' = 'MemberName' 'Computer Lockout On' = 'ComputerLockoutOn' 'Reported By' = 'ReportedBy' 'SamAccountName' = 'SamAccountName' 'Display Name' = 'DisplayName' 'UserPrincipalName' = 'UserPrincipalName' 'Home Directory' = 'HomeDirectory' 'Home Path' = 'HomePath' 'Script Path' = 'ScriptPath' 'Profile Path' = 'ProfilePath' 'User Workstation' = 'UserWorkstation' 'Password Last Set' = 'PasswordLastSet' 'Account Expires' = 'AccountExpires' 'Primary Group Id' = 'PrimaryGroupId' 'Allowed To Delegate To' = 'AllowedToDelegateTo' 'Old Uac Value' = 'OldUacValue' 'New Uac Value' = 'NewUacValue' 'User Account Control' = 'UserAccountControl' 'User Parameters' = 'UserParameters' 'Sid History' = 'SidHistory' 'Logon Hours' = 'LogonHours' 'OperationType' = 'OperationType' 'Message' = 'Message' 'Backup Path' = 'BackupPath' 'Log Type' = 'LogType' 'AddedWhen' = 'EventAdded,[datetime],null' 'AddedWho' = 'EventAddedWho' 'Gathered From' = 'GatheredFrom' 'Gathered LogName' = 'GatheredLogName' } } Events = @{Enabled = $true Events = 4720, 4738 LogName = 'Security' Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'ObjectAffected' = 'User Affected' 'SamAccountName' = 'SamAccountName' 'DisplayName' = 'DisplayName' 'UserPrincipalName' = 'UserPrincipalName' 'HomeDirectory' = 'Home Directory' 'HomePath' = 'Home Path' 'ScriptPath' = 'Script Path' 'ProfilePath' = 'Profile Path' 'UserWorkstations' = 'User Workstations' 'PasswordLastSet' = 'Password Last Set' 'AccountExpires' = 'Account Expires' 'PrimaryGroupId' = 'Primary Group Id' 'AllowedToDelegateTo' = 'Allowed To Delegate To' 'OldUacValue' = 'Old Uac Value' 'NewUacValue' = 'New Uac Value' 'UserAccountControl' = 'User Account Control' 'UserParameters' = 'User Parameters' 'SidHistory' = 'Sid History' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } Ignore = @{SubjectUserName = "ANONYMOUS LOGON" } Functions = @{'ProfilePath' = 'Convert-UAC' 'OldUacValue' = 'Remove-WhiteSpace', 'Convert-UAC' 'NewUacValue' = 'Remove-WhiteSpace', 'Convert-UAC' 'UserAccountControl' = 'Remove-WhiteSpace', 'Split-OnSpace', 'Convert-UAC' } IgnoreWords = @{ } SortBy = 'When' } } ADUserChangesDetailed = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 5136, 5137, 5139, 5141 LogName = 'Security' Filter = [ordered] @{'ObjectClass' = 'user' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'OperationType' = 'Action Detail' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'User Object' 'AttributeLDAPDisplayName' = 'Field Changed' 'AttributeValue' = 'Field Value' 'RecordID' = 'Record ID' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } } ADComputerChangesDetailed = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 5136, 5137, 5139, 5141 LogName = 'Security' Filter = @{'ObjectClass' = 'computer' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'OperationType' = 'Action Detail' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'Computer Object' 'AttributeLDAPDisplayName' = 'Field Changed' 'AttributeValue' = 'Field Value' 'RecordID' = 'Record ID' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } } ADOrganizationalUnitChangesDetailed = [ordered] @{Enabled = $false OUEventsModify = @{Enabled = $true Events = 5136, 5137, 5139, 5141 LogName = 'Security' Filter = [ordered] @{'ObjectClass' = 'organizationalUnit' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'OperationType' = 'Action Detail' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'Organizational Unit' 'AttributeLDAPDisplayName' = 'Field Changed' 'AttributeValue' = 'Field Value' 'RecordID' = 'Record ID' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } Overwrite = [ordered] @{'Action Detail#1' = 'Action', 'A directory service object was created.', 'Organizational Unit Created' 'Action Detail#2' = 'Action', 'A directory service object was deleted.', 'Organizational Unit Deleted' 'Action Detail#3' = 'Action', 'A directory service object was moved.', 'Organizational Unit Moved' } OverwriteByField = [ordered] @{'Organizational Unit' = 'Action', 'A directory service object was moved.', 'OldObjectDN' 'Field Value' = 'Action', 'A directory service object was moved.', 'NewObjectDN' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } } ADUserStatus = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4722, 4725, 4767, 4723, 4724, 4726 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'Who' = 'Who' 'Date' = 'When' 'ObjectAffected' = 'User Affected' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADUserLockouts = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4740 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetDomainName' = 'Computer Lockout On' 'ObjectAffected' = 'User Affected' 'Who' = 'Reported By' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADUserLogon = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 4624 LogName = 'Security' Fields = [ordered] @{'Computer' = 'Computer' 'Action' = 'Action' 'IpAddress' = 'IpAddress' 'IpPort' = 'IpPort' 'ObjectAffected' = 'User / Computer Affected' 'Who' = 'Who' 'Date' = 'When' 'LogonProcessName' = 'LogonProcessName' 'ImpersonationLevel' = 'ImpersonationLevel' 'VirtualAccount' = 'VirtualAccount' 'ElevatedToken' = 'ElevatedToken' 'LogonType' = 'LogonType' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } IgnoreWords = @{ } } } ADUserUnlocked = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4767 LogName = 'Security' IgnoreWords = @{ } Functions = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetDomainName' = 'Computer Lockout On' 'ObjectAffected' = 'User Affected' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADComputerCreatedChanged = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4741, 4742 LogName = 'Security' Ignore = @{SubjectUserName = "ANONYMOUS LOGON" } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'ObjectAffected' = 'Computer Affected' 'SamAccountName' = 'SamAccountName' 'DisplayName' = 'DisplayName' 'UserPrincipalName' = 'UserPrincipalName' 'HomeDirectory' = 'Home Directory' 'HomePath' = 'Home Path' 'ScriptPath' = 'Script Path' 'ProfilePath' = 'Profile Path' 'UserWorkstations' = 'User Workstations' 'PasswordLastSet' = 'Password Last Set' 'AccountExpires' = 'Account Expires' 'PrimaryGroupId' = 'Primary Group Id' 'AllowedToDelegateTo' = 'Allowed To Delegate To' 'OldUacValue' = 'Old Uac Value' 'NewUacValue' = 'New Uac Value' 'UserAccountControl' = 'User Account Control' 'UserParameters' = 'User Parameters' 'SidHistory' = 'Sid History' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } IgnoreWords = @{ } } } ADComputerDeleted = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 4743 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'ObjectAffected' = 'Computer Affected' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADUserLogonKerberos = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4768 LogName = 'Security' IgnoreWords = @{ } Functions = [ordered] @{'IpAddress' = 'Clean-IpAddress' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'ObjectAffected' = 'Computer/User Affected' 'IpAddress' = 'IpAddress' 'IpPort' = 'Port' 'TicketOptions' = 'TicketOptions' 'Status' = 'Status' 'TicketEncryptionType' = 'TicketEncryptionType' 'PreAuthType' = 'PreAuthType' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADGroupMembershipChanges = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 4728, 4729, 4732, 4733, 4746, 4747, 4751, 4752, 4756, 4757, 4761, 4762, 4785, 4786, 4787, 4788 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetUserName' = 'Group Name' 'MemberNameWithoutCN' = 'Member Name' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADGroupEnumeration = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 4798, 4799 LogName = 'Security' IgnoreWords = [ordered] @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetUserName' = 'Group Name' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADGroupChanges = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 4735, 4737, 4745, 4750, 4760, 4764, 4784, 4791 LogName = 'Security' IgnoreWords = @{'Who' = '*ANONYMOUS*' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetUserName' = 'Group Name' 'Who' = 'Who' 'Date' = 'When' 'GroupTypeChange' = 'Changed Group Type' 'SamAccountName' = 'Changed SamAccountName' 'SidHistory' = 'Changed SidHistory' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADGroupCreateDelete = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 4727, 4730, 4731, 4734, 4744, 4748, 4749, 4753, 4754, 4758, 4759, 4763 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'TargetUserName' = 'Group Name' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } ADGroupChangesDetailed = [ordered] @{Enabled = $false Events = @{Enabled = $true Events = 5136, 5137, 5141 LogName = 'Security' Filter = [ordered] @{'ObjectClass' = 'group' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'OperationType' = 'Action Detail' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'Computer Object' 'ObjectClass' = 'ObjectClass' 'AttributeLDAPDisplayName' = 'Field Changed' 'AttributeValue' = 'Field Value' 'RecordID' = 'Record ID' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } } ADGroupPolicyChanges = [ordered] @{Enabled = $false 'Group Policy Name Changes' = @{Enabled = $true Events = 5136, 5137, 5141 LogName = 'Security' Filter = [ordered] @{'ObjectClass' = 'groupPolicyContainer' 'AttributeLDAPDisplayName' = $null, 'displayName' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'RecordID' = 'Record ID' 'Computer' = 'Domain Controller' 'Action' = 'Action' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'ObjectDN' 'ObjectGUID' = 'ObjectGUID' 'ObjectClass' = 'ObjectClass' 'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName' 'AttributeValue' = 'AttributeValue' 'OperationType' = 'OperationType' 'OpCorrelationID' = 'OperationCorelationID' 'AppCorrelationID' = 'OperationApplicationCorrelationID' 'DSName' = 'DSName' 'DSType' = 'DSType' 'Task' = 'Task' 'Version' = 'Version' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } 'Group Policy Edits' = @{Enabled = $true Events = 5136, 5137, 5141 LogName = 'Security' Filter = [ordered] @{'ObjectClass' = 'groupPolicyContainer' 'AttributeLDAPDisplayName' = 'versionNumber' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'RecordID' = 'Record ID' 'Computer' = 'Domain Controller' 'Action' = 'Action' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'ObjectDN' 'ObjectGUID' = 'ObjectGUID' 'ObjectClass' = 'ObjectClass' 'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName' 'AttributeValue' = 'AttributeValue' 'OperationType' = 'OperationType' 'OpCorrelationID' = 'OperationCorelationID' 'AppCorrelationID' = 'OperationApplicationCorrelationID' 'DSName' = 'DSName' 'DSType' = 'DSType' 'Task' = 'Task' 'Version' = 'Version' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } 'Group Policy Links' = @{Enabled = $true Events = 5136, 5137, 5141 LogName = 'Security' Filter = @{'ObjectClass' = 'domainDNS' } Functions = @{'OperationType' = 'ConvertFrom-OperationType' } Fields = [ordered] @{'RecordID' = 'Record ID' 'Computer' = 'Domain Controller' 'Action' = 'Action' 'Who' = 'Who' 'Date' = 'When' 'ObjectDN' = 'ObjectDN' 'ObjectGUID' = 'ObjectGUID' 'ObjectClass' = 'ObjectClass' 'AttributeLDAPDisplayName' = 'AttributeLDAPDisplayName' 'AttributeValue' = 'AttributeValue' 'OperationType' = 'OperationType' 'OpCorrelationID' = 'OperationCorelationID' 'AppCorrelationID' = 'OperationApplicationCorrelationID' 'DSName' = 'DSName' 'DSType' = 'DSType' 'Task' = 'Task' 'Version' = 'Version' 'ID' = 'Event ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'Record ID' Descending = $false IgnoreWords = @{ } } } ADLogsClearedSecurity = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 1102, 1105 LogName = 'Security' Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'BackupPath' = 'Backup Path' 'Channel' = 'Log Type' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' IgnoreWords = @{ } Overwrite = [ordered] @{'Backup Path' = 'Backup Path', '', 'N/A' 'Who' = 'Event ID', 1105, 'Automatic Backup' } } } ADLogsClearedOther = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 104 LogName = 'System' IgnoreWords = @{ } Fields = [ordered] @{'Computer' = 'Domain Controller' 'Action' = 'Action' 'BackupPath' = 'Backup Path' 'Channel' = 'Log Type' 'Who' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' } SortBy = 'When' Overwrite = @{'Backup Path' = 'Backup Path', '', 'N/A' } } } NetworkAccessAuthenticationPolicy = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 6272, 6273 LogName = 'Security' IgnoreWords = @{ } Fields = [ordered] @{'Action' = 'Action' 'SubjectUserSid' = 'SecurityID' 'Computer' = 'Compuer' 'SubjectUserName' = 'AccountName' 'SubjectDomainName' = 'Account Domain' 'CalledStationID' = 'CalledStationID' 'CallingStationID' = 'CallingStationID' 'NASIPv4Address' = 'NASIPv4Address' 'NASIPv6Address' = 'NASIPv6Address' 'NASIdentifier' = 'NASIdentifier' 'NASPortType' = 'NASPortType' 'NASPort' = 'NASPort' 'ClientName' = 'ClientFriendlyName' 'ClientIPAddress' = 'ClientFriendlyIPAddress' 'ProxyPolicyName' = 'ConnectionRequestPolicyName' 'NetworkPolicyName' = 'NetworkPolicyName' 'AuthenticationProvider' = 'AuthenticationProvider' 'AuthenticationServer' = 'AuthenticationServer' 'AuthenticationType' = 'AuthenticationType' 'EAPType' = 'EAPType' 'Reason' = 'Reason' 'ReasonCode' = 'ReasonCode' 'FullyQualifiedSubjectUserName' = 'Who' 'Date' = 'When' 'ID' = 'Event ID' 'RecordID' = 'Record ID' 'GatheredFrom' = 'Gathered From' 'GatheredLogName' = 'Gathered LogName' } SortBy = 'When' } } "OSCrash" = [ordered]@{Enabled = $false Events = @{Enabled = $true Events = 6008 LogName = 'System' IgnoreWords = @{ } Fields = [ordered] @{"Computer" = "Computer" 'Date' = 'When' "MachineName" = "ObjectAffected" "EventAction" = "Action" "Message" = "ActionDetails" "NoNameA1" = "ActionDetailsDate" "NoNameA0" = "ActionDetailsTime" "ID" = "Event ID" "RecordID" = "Record ID" "GatheredFrom" = "Gathered From" "GatheredLogName" = "Gathered LogName" } Overwrite = @{"Action#1" = "Event ID" , 6008, "System Crash" } } } "OSStartupShutdownCrash" = [ordered]@{Enabled = $false Events = [ordered] @{Enabled = $true Events = 12, 13, 41, 4608, 4621, 6008 LogName = 'System' IgnoreWords = @{ } Filter = [ordered] @{'ProviderName' = 'Microsoft-Windows-Kernel-General', 'EventLog' } FilterOr = [ordered] @{ } Fields = [ordered] @{"Computer" = "Computer" 'Date' = 'When' "MachineName" = "ObjectAffected" "EventAction" = "Action" "Message" = "ActionDetails" "NoNameA1" = "ActionDetailsDate" "NoNameA0" = "ActionDetailsTime" "ActionDetailsDateTime" = "ActionDetailsDateTime" "ID" = "Event ID" "RecordID" = "Record ID" "GatheredFrom" = "Gathered From" "GatheredLogName" = "Gathered LogName" } Overwrite = [ordered] @{"Action#1" = "Event ID", 12, "System Start" "Action#2" = "Event ID", 13, "System Shutdown" "Action#3" = "Event ID", 41, "System Dirty Reboot" "Action#4" = "Event ID", 4608, "Windows is starting up" "Action#5" = "Event ID", 4621, "Administrator recovered system from CrashOnAuditFail" "Action#6" = "Event ID", 6008, "System Crash" } OverwriteByField = @{'ActionDetailsDateTime#1#ne' = 'StartTime', $null, 'StartTime' 'ActionDetailsDateTime#2#ne' = '#text', $null, '#text' } } } LdapBindingsDetails = $LdapBindingsDetails LdapBindingsSummary = $LdapBindingsSummary } $Script:ReportTimes = [ordered] @{PastHour = @{Enabled = $false } CurrentHour = @{Enabled = $false } PastDay = @{Enabled = $false } CurrentDay = @{Enabled = $false } OnDay = @{Enabled = $false Days = 'Monday' } PastMonth = @{Enabled = $false Force = $true } CurrentMonth = @{Enabled = $false } PastQuarter = @{Enabled = $false Force = $true } CurrentQuarter = @{Enabled = $false } CurrentDayMinusDayX = @{Enabled = $false Days = 7 } CurrentDayMinuxDaysX = @{Enabled = $false Days = 3 } CustomDate = @{Enabled = $false DateFrom = Get-Date -Year 2018 -Month 03 -Day 19 DateTo = Get-Date -Year 2018 -Month 03 -Day 23 } Last3days = @{Enabled = $false } Last7days = @{Enabled = $false } Last14days = @{Enabled = $false } Everything = @{Enabled = $false } } function Add-ServersToXML { [CmdletBinding()] param ([string] $FilePath, [string[]] $Servers) [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8 foreach ($Server in $Servers) { $node = $xmlDocument.CreateElement('EventSource', $xmlDocument.Subscription.NamespaceURI) $node.SetAttribute('Enabled', 'true') $nodeServer = $xmlDocument.CreateElement('Address', $xmlDocument.Subscription.NamespaceURI) $nodeServer.set_InnerXML($Server) [void] $xmlDocument.Subscription.Eventsources.AppendChild($node) [void] $xmlDocument.Subscription.Eventsources.EventSource.AppendChild($nodeServer) } Save-XML -FilePath $FilePath -xml $xmlDocument } function Set-SubscriptionTemplates { [CmdletBinding()] param([System.Array] $ListTemplates, [switch] $DeleteOwn, [switch] $DeleteAllOther, [System.Collections.IDictionary] $LoggerParameters) if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters if ($DeleteAll -or $DeleteOwn) { Remove-Subscription -All:$DeleteAllOther -Own:$DeleteOwn -LoggerParameters $LoggerParameters } foreach ($TemplatePath in $ListTemplates) { $Logger.AddInfoRecord("Adding provider $TemplatePath to Subscriptions.") Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'cs', $TemplatePath -LoggerParameters $LoggerParameters } } function Add-EventsDefinitions { [CmdLetBinding()] param([parameter(Mandatory = $true)][System.Collections.IDictionary] $Definitions, [parameter(Mandatory = $true)][string] $Name, [switch] $Force) $AllDefinitions = Get-EventsDefinitions -Definitions $Definitions if ($null -ne $AllDefinitions) { [string] $ConfigurationPath = "$Env:ALLUSERSPROFILE\Evotec\PSWinReporting\Definitions" $null = New-Item -Type Directory -Path $ConfigurationPath -Force if (Test-Path -LiteralPath $ConfigurationPath) { $XMLPath = "$ConfigurationPath\$Name.xml" if ((Test-Path -LiteralPath $XMLPath) -and (-not $Force)) { Write-Warning -Message "Definition with name $Name already exists. Please choose another name or use -Force switch." return } $Definitions | Export-Clixml -LiteralPath $XMLPath -Depth 5 } } } function Add-WinTaskScheduledForwarder { [CmdletBinding()] param([string] $TaskPath = '\Event Viewer Tasks\', [string] $TaskName = 'ForwardedEvents', [string] $Author = 'Evotec', [string] $URI = '\Event Viewer Tasks\ForwardedEvents', [string] $Command = 'powershell.exe', [Array] $Argument = @('-windowstyle hidden', 'C:\Support\GitHub\PSWinReporting\Examples\Trigger.ps1', "-EventID $(eventID) -eventRecordID '$(eventRecordID)' -eventChannel '$(eventChannel)' -eventSeverity $(eventSeverity)"), [System.Collections.IDictionary] $LoggerParameters) if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters $xmlTemplate = "$($($(Get-Module -ListAvailable PSWinReportingV2)[0]).ModuleBase)\Templates\Template-ScheduledTask.xml" if (Test-Path -LiteralPath $xmlTemplate) { $Logger.AddInfoRecord("Found Template $xmlTemplate") $ScheduledTaskXML = "$ENV:TEMP\PSWinReportingSchedluledTask.xml" Copy-Item -Path $xmlTemplate -Destination $ScheduledTaskXML $Logger.AddInfoRecord("Copied template $ScheduledTaskXML") Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'RegistrationInfo' -Node 'Author' -Value $Author Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'Actions', 'Exec' -Node 'Command' -Value $Command Set-XML -FilePath $ScheduledTaskXML -Paths 'Task', 'Actions', 'Exec' -Node 'Arguments' -Value ([string] $Argument) $xml = (Get-Content -LiteralPath $ScheduledTaskXML | Out-String) try { $Output = Register-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -xml $xml -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " switch ($ErrorMessage) { default { $Logger.AddErrorRecord("Tasks adding error occured: $ErrorMessage") } } Exit } $Logger.AddInfoRecord("Loaded template $ScheduledTaskXML") } else { $Logger.AddErrorRecord("Template not found $xmlTemplate") } } function Find-Events { [CmdLetBinding(DefaultParameterSetName = 'Manual')] param([parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "Manual", Mandatory = $true)][DateTime] $DateFrom, [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "Manual", Mandatory = $true)][DateTime] $DateTo, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange", Mandatory = $false)][alias('Server', 'ComputerName')][string[]] $Servers = $Env:COMPUTERNAME, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange", Mandatory = $false)][alias('RunAgainstDC')][switch] $DetectDC, [ValidateNotNull()] [alias('Credentials')][System.Management.Automation.PSCredential] [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][switch] $Quiet, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][System.Collections.IDictionary] $LoggerParameters, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][switch] $ExtentedOutput, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][string] $Who, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][string] $Whom, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][string] $NotWho, [parameter(ParameterSetName = "Manual")] [parameter(ParameterSetName = "DateManual")] [parameter(ParameterSetName = "DateRange")][string] $NotWhom, [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Definitions, [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Times, [parameter(ParameterSetName = "Extended", Mandatory = $true)][System.Collections.IDictionary] $Target, [parameter(ParameterSetName = "Extended", Mandatory = $false)][int] $EventID, [parameter(ParameterSetName = "Extended", Mandatory = $false)][int64] $EventRecordID) DynamicParam { $ParameterSetsAttributesDateManual = New-Object System.Management.Automation.ParameterAttribute $ParameterSetsAttributesDateManual.Mandatory = $true $ParameterSetsAttributesDateManual.ParameterSetName = 'DateManual' $ParamAttribDatesRange = New-Object System.Management.Automation.ParameterAttribute $ParamAttribDatesRange.Mandatory = $true $ParamAttribDatesRange.ParameterSetName = 'DateRange' $ParameterSetsAttributes = New-Object System.Management.Automation.ParameterAttribute $ParameterSetsAttributes.Mandatory = $true $ParameterSetsAttributes.ParameterSetName = 'Manual' $Names = (Get-EventsDefinitions).Keys $ReportAttrib = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $ReportAttrib.Add($ParameterSetsAttributes) $ReportAttrib.Add($ParamAttribDatesRange) $ReportAttrib.Add($ParameterSetsAttributesDateManual) $ReportAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($Names))) $ReportRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Report', [string[]], $ReportAttrib) $DatesRange = (Get-DatesDefinitions -Skip 'CustomDate', 'CurrentDayMinuxDaysX', 'CurrentDayMinusDayX', 'OnDay') $DatesRangeAttrib = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $DatesRangeAttrib.Add($ParamAttribDatesRange) $DatesRangeAttrib.Add((New-Object System.Management.Automation.ValidateSetAttribute($DatesRange))) $DatesRangeRuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('DatesRange', [string], $DatesRangeAttrib) $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary $RuntimeParamDic.Add('Report', $ReportRuntimeParam) $RuntimeParamDic.Add('DatesRange', $DatesRangeRuntimeParam) return $RuntimeParamDic } Process { foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false } foreach ($Time in $Script:ReportTimes.Keys) { $Script:ReportTimes[$Time].Enabled = $false } $ExecutionTime = Start-TimeLog if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) { $Verbose = $true } else { $Verbose = $false } if ($null -ne $Definitions -and $null -ne $Times -and $null -ne $Target) { $Dates = Get-ChoosenDates -ReportTimes $Times if (-not $Dates) { $Logger.AddErrorRecord("Not a single date was choosen for scan. Please fix Times and try again.") return } if ($Dates -is [Array]) { $Logger.AddErrorRecord("Currently only 1 date range is supported. Please fix Times and try again.") return } $Reports = foreach ($Report in $Definitions.Keys) { if ($Definitions[$Report].Enabled -eq $true) { $Report } } } else { $Reports = $PSBoundParameters.Report $DatesRange = $PSBoundParameters.DatesRange if (-not $Quiet) { $Logger.AddInfoRecord("Preparing reports: $($Reports -join ',')") } $Definitions = $Script:ReportDefinitions $Times = $Script:ReportTimes if ($DatesRange) { $Times.$DatesRange.Enabled = $true } elseif ($DateFrom -and $DateTo) { $Times.CustomDate.Enabled = $true $Times.CustomDate.DateFrom = $DateFrom $Times.CustomDate.DateTo = $DateTo } else { return } $Dates = Get-ChoosenDates -ReportTimes $Times if (-not $Dates) { $Logger.AddErrorRecord("Not a single date was choosen for scan. Please fix Times and try again.") return } if ($Dates -is [Array]) { $Logger.AddErrorRecord("Currently only 1 date range is supported. Please fix Times and try again") return } foreach ($Report in $Reports) { $Definitions[$Report].Enabled = $true } $Target = New-TargetServers -Servers $Servers -UseDC:$DetectDC } if ($EventRecordID -ne 0 -and $EventID -ne 0) { [Array] $ExtendedInput = Get-ServersListLimited -Target $Target -RecordID $EventRecordID -Quiet:$Quiet -Who $Who -Whom $Whom -NotWho $NotWho -NotWhom $NotWhom } else { [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target -Dates $Dates -Quiet:$Quiet -Who $Who -Whom $Whom -NotWho $NotWho -NotWhom $NotWhom } if (-not $ExtendedInput) { $Logger.AddErrorRecord("There are no logs/servers to scan. Please fix Targets and try again.") return } foreach ($Entry in $ExtendedInput) { if ($Entry.Type -eq 'Computer') { if (-not $Quiet) { $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } else { if (-not $Quiet) { $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } } if (-not $Quiet) { $Logger.AddInfoRecord("Getting events for dates $($Dates.DateFrom) to $($Dates.DateTo)") } $SplatEvents = @{Verbose = $Verbose ExtendedInput = $ExtendedInput ErrorVariable = 'AllErrors' ErrorAction = 'SilentlyContinue' } if ($EventRecordID -ne 0 -and $EventId -ne 0) { $SplatEvents.RecordID = $EventRecordID $SplatEvents.ID = $EventID } if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $SplatEvents.Credential = $Credential } [Array] $AllEvents = Get-Events @SplatEvents foreach ($MyError in $AllErrors) { if (-not $Quiet) { $Logger.AddErrorRecord("Server $MyError") } } $Elapsed = Stop-TimeLog -Time $ExecutionTime -Option OneLiner if (-not $Quiet) { $Logger.AddInfoRecord("Events scanned found $($AllEvents.Count) - Time elapsed: $Elapsed") } $Results = Get-EventsOutput -Definitions $Definitions -AllEvents $AllEvents -Quiet:$Quiet if ($Results.Count -eq 1) { $Results[$Reports] } else { $Results } foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false } foreach ($Time in $Script:ReportTimes.Keys) { $Script:ReportTimes[$Time].Enabled = $false } } } function New-WinSubscriptionTemplates { [CmdletBinding()] param ([string[]] $Servers, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [ValidateScript( { $_ -in (& $SourcesAutoCompleter) })][string[]] $Reports, [switch] $AddTemplates, [alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions, [System.Collections.IDictionary] $Target, [System.Collections.IDictionary] $LoggerParameters) Begin { if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters if (-not $Reports) { $Reports = (Get-EventsDefinitions).Keys } if (-not $Definitions) { $Definitions = $Script:ReportDefinitions } foreach ($Report in $Reports) { $Definitions[$Report].Enabled = $true } if (-not $Target) { $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC $Target = [ordered]@{Servers = [ordered] @{Enabled = $true ServerDCs = $ForestInformation.ForestDomainControllers.HostName ServerOther = $Servers } } } } Process { [Array] $ExtendedInput = Get-ServersList -Definitions $Definitions -Target $Target foreach ($Entry in $ExtendedInput) { if ($Entry.Type -eq 'Computer') { $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } else { $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } $xmlTemplate = "$($($(Get-Module -ListAvailable PSWinReportingV2)[0]).ModuleBase)\Templates\Template-Collector.xml" if (Test-Path -LiteralPath $xmlTemplate) { $Logger.AddInfoRecord("Found Template $xmlTemplate") $SubscriptionCount = 0 $InputServers = ($ExtendedInput | Group-Object -Property LogName) $ListTemplates = foreach ($InputData in $InputServers) { $Servers = $InputData.Group.Server $EventID = $InputData.Group.EventID | Select-Object -Unique $LogName = $InputData.Name $SplitArrayID = Split-Array -inArray $EventID -size 22 $Array = foreach ($ID in $SplitArrayID) { Get-EventsFilter -ID $ID -LogName $LogName } foreach ($Events in $Array) { $SubscriptionCount++ $SubscriptionTemplate = "$ENV:TEMP\PSWinReportingSubscription$SubscriptionCount.xml" Copy-Item -Path $xmlTemplate -Destination $SubscriptionTemplate $Logger.AddInfoRecord("Copied template $SubscriptionTemplate") Add-ServersToXML -FilePath $SubscriptionTemplate -Servers $Servers Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'SubscriptionId' -Value "PSWinReporting Subscription Events - $SubscriptionCount" Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ContentFormat' -Value 'Events' Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'ConfigurationMode' -Value 'Custom' Set-XML -FilePath $SubscriptionTemplate -Path 'Subscription' -Node 'Query' -Value $Events $SubscriptionTemplate } } } else { $Logger.AddInfoRecord("Template not found $xmlTemplate") } if ($AddTemplates) { Set-SubscriptionTemplates -ListTemplates $ListTemplates -DeleteOwn -LoggerParameters $LoggerParameters } } End { foreach ($Report in $Script:ReportDefinitions.Keys) { $Script:ReportDefinitions[$Report].Enabled = $false } } } [scriptblock] $SourcesAutoCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Reports = (Get-EventsDefinitions).Keys $Reports | Sort-Object } Register-ArgumentCompleter -CommandName New-WinSubscriptionTemplates -ParameterName Reports -ScriptBlock $SourcesAutoCompleter function Remove-WinTaskScheduledForwarder { [CmdletBinding()] param([string] $TaskPath = '\Event Viewer Tasks\', [string] $TaskName = 'ForwardedEvents', [System.Collections.IDictionary] $LoggerParameters) if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters try { Unregister-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -Confirm:$false -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " switch ($ErrorMessage) { { $_ -match 'No matching MSFT_ScheduledTask objects found by CIM query for instances of the' } { $Logger.AddInfoRecord("No tasks exists. Nothing to remove") } default { $Logger.AddErrorRecord("Tasks removal error: $ErrorMessage") } } } } function Start-WinNotifications { [CmdletBinding()] param([System.Collections.IDictionary] $Options, [System.Collections.IDictionary] $Definitions, [System.Collections.IDictionary] $Target, [int] $EventID, [int64] $EventRecordID, [string] $EventChannel) if ($Options.Logging) { $LoggerParameters = $Options.Logging } else { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters $Results = @{ } $Logger.AddInfoRecord("Executed Trigger for ID: $eventid and RecordID: $eventRecordID") $Logger.AddInfoRecord("Using Microsoft Teams: $($Options.Notifications.MicrosoftTeams.Enabled)") if ($Options.Notifications.MicrosoftTeams.Enabled) { foreach ($Priority in $Options.Notifications.MicrosoftTeams.Keys | Where-Object { $_ -notcontains 'Enabled' }) { [string] $URI = Format-FirstXChars -Text $Options.Notifications.MicrosoftTeams.$Priority.Uri -NumberChars 50 $Logger.AddInfoRecord("Priority: $Priority, TeamsID: $URI...") } } $Logger.AddInfoRecord("Using Slack: $($Options.Notifications.Slack.Enabled)") if ($Options.Notifications.Slack.Enabled) { foreach ($Priority in $Options.Notifications.Slack.Keys | Where-Object { $_ -notcontains 'Enabled' }) { [string] $URI = Format-FirstXChars -Text $Options.Notifications.Slack.$Priority.URI -NumberChars 25 $Logger.AddInfoRecord("Priority: $Priority, Slack URI: $URI...") $Logger.AddInfoRecord("Priority: $Priority, Slack Channel: $($($Options.Notifications.Slack.$Priority.Channel))...") } } $Logger.AddInfoRecord("Using Discord: $($Options.Notifications.Discord.Enabled)") if ($Options.Notifications.Discord.Enabled) { foreach ($Priority in $Options.Notifications.Discord.Keys | Where-Object { $_ -notcontains 'Enabled' }) { [string] $URI = Format-FirstXChars -Text $Options.Notifications.Discord.$Priority.URI -NumberChars 25 $Logger.AddInfoRecord("Priority: $Priority, Discord URI: $URI...") } } $Logger.AddInfoRecord("Using MSSQL: $($Options.Notifications.MSSQL.Enabled)") if ($Options.Notifications.MSSQL.Enabled) { foreach ($Priority in $Options.Notifications.MSSQL.Keys | Where-Object { $_ -notcontains 'Enabled' }) { $Logger.AddInfoRecord("Priority: $Priority, Server\Instance: $($Options.Notifications.MSSQL.$Priority.SqlServer)") $Logger.AddInfoRecord("Priority: $Priority, Database: $($Options.Notifications.MSSQL.$Priority.SqlDatabase)") } } $Logger.AddInfoRecord("Using Email: $($Options.Notifications.Email.Enabled)") if ($Options.Notifications.Email.Enabled) { foreach ($Priority in $Options.Notifications.Email.Keys | Where-Object { 'Enabled', 'Formatting' -notcontains $_ }) { $Logger.AddInfoRecord("Priority: $Priority, Email TO: $($Options.Notifications.Email.$Priority.Parameters.To), Email CC: $($Options.Notifications.Email.$Priority.Parameters.CC)") } } if (-not $Options.Notifications.Slack.Enabled -and -not $Options.Notifications.MicrosoftTeams.Enabled -and -not $Options.Notifications.MSSQL.Enabled -and -not $Options.Notifications.Discord.Enabled -and -not $Options.Notifications.Email.Enabled) { return } [Array] $ExtendedInput = Get-ServersListLimited -Target $Target -RecordID $EventRecordID foreach ($Entry in $ExtendedInput) { if ($Entry.Type -eq 'Computer') { $Logger.AddInfoRecord("Computer $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } else { $Logger.AddInfoRecord("File $($Entry.Server) added to scan $($Entry.LogName) log for events: $($Entry.EventID -join ', ')") } } $AllEvents = Get-Events -ExtendedInput $ExtendedInput -EventID $eventid -RecordID $eventRecordID -Verbose:$Options.Debug.Verbose foreach ($Report in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled' }) { if ($Definitions.$Report.Enabled) { $Logger.AddInfoRecord("Running $Report") $TimeExecution = Start-TimeLog foreach ($SubReport in $Definitions.$Report.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport' }) { if ($Definitions.$Report.$SubReport.Enabled) { $Logger.AddInfoRecord("Running $Report with subsection $SubReport") [string] $EventsType = $Definitions.$Report.$SubReport.LogName [Array] $EventsNeeded = $Definitions.$Report.$SubReport.Events [Array] $EventsFound = Get-EventsTranslation -Events $AllEvents -EventsDefinition $Definitions.$Report.$SubReport -EventIDs $EventsNeeded -EventsType $EventsType $Logger.AddInfoRecord("Ending $Report with subsection $SubReport events found $($EventsFound.Count)") $Results.$Report = $EventsFound } } $ElapsedTimeReport = Stop-TimeLog -Time $TimeExecution -Option OneLiner $Logger.AddInfoRecord("Ending $Report - Time to run $ElapsedTimeReport") } } [bool] $FoundPriorityEvent = $false foreach ($ReportName in $Definitions.Keys | Where-Object { $_ -notcontains 'Enabled', 'SqlExport', 'Priority' }) { if ($Results.$ReportName) { if ($null -ne $Definitions.$ReportName.Priority) { foreach ($Priority in $Definitions.$ReportName.Priority.Keys) { [Array] $MyValue = Find-EventsTo -Prioritize -Events $Results.$ReportName -DataSet $Definitions.$ReportName.Priority.$Priority if ($MyValue.Count) { $Logger.AddInfoRecord("Sending event with $Priority priority.") Send-Notificaton -Events $MyValue -Options $Options -Priority $Priority $FoundPriorityEvent = $true } } } if (-not $FoundPriorityEvent) { $Logger.AddInfoRecord("Sending event with default priority.") Send-Notificaton -Events $Results.$ReportName -Options $Options -Priority 'Default' } } } if ($Options.Backup.Enabled) { Protect-ArchivedLogs -TableEventLogClearedLogs $TableEventLogClearedLogs -DestinationPath $Options.Backup.DestinationPath -Verbose:$Options.Debug.Verbose } } function Start-WinReporting { [CmdletBinding()] param ([Parameter(Mandatory = $true)][System.Collections.IDictionary]$Times, [Parameter(Mandatory = $true)][alias('ReportOptions')][System.Collections.IDictionary] $Options, [Parameter(Mandatory = $true)][alias('ReportDefinitions')][System.Collections.IDictionary] $Definitions, [Parameter(Mandatory = $true)][alias('Servers', 'Computers')][System.Collections.IDictionary] $Target) if ($Options.Logging) { $LoggerParameters = $Options.Logging } else { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters $Dates = Get-ChoosenDates -ReportTimes $Times foreach ($Date in $Dates) { $Logger.AddInfoRecord("Starting to build a report for dates $($Date.DateFrom) to $($Date.DateTo)") Start-ReportSpecial -Dates $Date -Options $Options -Definitions $Definitions -Target $Target } } function Start-WinSubscriptionService { [CmdletBinding()] param([System.Collections.IDictionary] $LoggerParameters) if (-not $LoggerParameters) { $LoggerParameters = $Script:LoggerParameters } $Logger = Get-Logger @LoggerParameters $Logger.AddInfoRecord('Starting Windows Event Collector service.') $Output = Start-MyProgram -Program $Script:ProgramWecutil -cmdArgList 'qc', '/q:true' $Logger.AddInfoRecord($Output) } Export-ModuleMember -Function @('Add-EventsDefinitions', 'Add-WinTaskScheduledForwarder', 'Find-Events', 'New-WinSubscriptionTemplates', 'Remove-WinTaskScheduledForwarder', 'Start-WinNotifications', 'Start-WinReporting', 'Start-WinSubscriptionService') -Alias @() |