HardenSysvol.psm1
|
<#
.SYNOPSIS Scans the SYSVOL folder for sensitive or risky content. .DESCRIPTION This module audits the SYSVOL directory for files containing sensitive data (passwords, usernames, certificates, configs, etc.). It helps identify security risks and integrity issues to harden Active Directory environments. .VERSION 2.1 .Contribution Credit : HardenAD Community HardenAD Credit : It-connect Community It-Connect .AUTHOR DAKHAMA Mehdi .PARAMETER dnsDomain Specifies the DNS domain to be scanned. Defaults to the current user's DNS domain if not provided. .PARAMETER ignoreExtensions Specifies file extensions to ignore during the scan. .EXAMPLE # Scan the Sysvol folder of the current domain Invoke-HardenSysvol # Scan the Sysvol folder of a specific domain, ignoring .txt and .log files Invoke-HardenSysvol -dnsDomain "example.com" -ignoreExtensions "txt", "log" -Addpattern admin -AddExtensions adml,admx,adm .NOTES This script not requires administrative privileges to access and scan the Sysvol directory. .LINK https://github.com/dakhama-mehdi/Harden-Sysvol #> function Invoke-HardenSysvol { <# .SYNOPSIS Scans the SYSVOL folder for sensitive or risky content. .DESCRIPTION This module audits the SYSVOL directory for files containing sensitive data (passwords, usernames, certificates, configs, etc.). It helps identify security risks and integrity issues to harden Active Directory environments. .PARAMETER dnsDomain Specifies the DNS domain to be scanned. Defaults to the current user's DNS domain if not provided. .PARAMETER ignoreExtensions Specifies file extensions to ignore during the scan. .EXAMPLE Scan the Sysvol folder of the current domain Invoke-HardenSysvol .EXAMPLE Scan the Sysvol folder of a specific domain, ignoring .txt and .log files Invoke-HardenSysvol -dnsDomain "example.com" -ignoreExtensions "txt","log" -Addpattern admin -AddExtensions adml,admx,adm .EXAMPLE Scan include all extensions Invoke-HardenSysvol -Allextensions .EXAMPLE Scan include Policydefintions Invoke-HardenSysvol -Allextensions -Includepolicydefinitions .NOTES This script not requires administrative privileges to access and scan the Sysvol directory. .LINK https://github.com/dakhama-mehdi/Harden-Sysvol #> [CmdletBinding()] Param( [Parameter(Mandatory = $false)] [String]$dnsDomain = $env:USERDNSDOMAIN, [Parameter(Mandatory = $false)] [String[]]$ignoreExtensions, [Parameter(Mandatory = $false)] [String[]]$AddExtensions, [Parameter(Mandatory = $false)] [String[]]$Addpattern, [Parameter(Mandatory = $false)] [String[]]$removepattern, #Scann all extensions [Parameter(ValueFromPipeline = $true, HelpMessage = "Scann all extension")] [switch]$Allextensions, #Location the report will be saved [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter desired directory path to save; Default: %temp%")] [String]$SavePath = $env:TEMP, #Location to export CSV [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter desired directory path to save; Default: %temp%")] [String]$Exportcsv, #Location the report will be saved [Parameter(ValueFromPipeline = $true, HelpMessage = "maximum size of the file to be considered large in MB")] [int]$Maxfilesize = '10', #Location the report will be saved [Parameter(ValueFromPipeline = $true, HelpMessage = "maximum size of the binary to be considered large in MB")] [String]$MaxBinarysize = '50', # Include: scans the 'PolicyDefinitions' folder, which contains added .admx and .adml templates [Parameter(HelpMessage = "Include files from the 'Policies\\PolicyDefinitions' folder (central store for ADMX/ADML templates).")] [switch]$IncludePolicyDefinitions, # Checks all shared folders on each domain controller and lists custom shares (excluding default and DFS links) [Parameter(HelpMessage = "Scan all shares on all domain controllers and list non-default ones (skipping DFS links)")] [switch]$CheckShares, #Option to use if no want to start automatically hmtl page, used on script. [Parameter(ValueFromPipeline = $true, HelpMessage = "No auto start html page")] [switch]$Silent, #Location Custom pattern file [Parameter(ValueFromPipeline = $true, HelpMessage = "Enter Custome XML file path; Default: %module%\patterns.xml")] [String] [ValidateScript({ if ($_ -match '\.xml$') { return $true } else { throw "The file must have a .xml extension." } })]$Custompatterns ) $Bannercolor = "Yellow" $MSGcolor = "DarkGreen" function Show-HardenSysvolBanner { param ( [string]$BannerColor ) Write-Host "" Write-Host "╔═════════════════════════════════════════════════════╗" -ForegroundColor $Bannercolor Write-Host "║ Welcome to HardenSysvol v2.2.5 ║" -ForegroundColor $Bannercolor Write-Host "║ Auditing Active Directory SYSVOL & GPOs ║" -ForegroundColor $Bannercolor Write-Host "║ Developed by HardenAD Community ║" -ForegroundColor $Bannercolor Write-Host "╚═════════════════════════════════════════════════════╝" -ForegroundColor $Bannercolor Write-Host "" } Show-HardenSysvolBanner -BannerColor $Bannercolor #region script #region code #region load prerequist # Test access to the share $sysvolRoot = "\\$dnsDomain\sysvol\" if ((Test-Path $sysvolRoot) -eq $false) { # Try check for logon server $sysvolRoot = "$env:LOGONSERVER\sysvol" if ((Test-Path $sysvolRoot) -eq $false) { throw "Cannot access domain or share, pls check with Get-Childrenitem $dnsDomain" } } # Test Modules $modulesToCheck = @("PSWriteOffice", "PSWritePDF", "PSWriteHTML") foreach ($module in $modulesToCheck) { try { # Check if module installed if (!(Get-Module -ListAvailable -Name $module)) { Write-Output "Installing module: $module." install-Module -Name $module -Force -Scope CurrentUser -ErrorAction Ignore Write-Output "The module $module has been successfully installed" } } catch { Write-Error "Error while installing module $module : $_" throw "Script stopped due to an error during module installation $module." } } # Check about Word office if installed or not to read old version doc,xls function Is-WordInstalled { try { $wordApp = New-Object -ComObject Word.Application # If the COM object is created successfully, Word is installed [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wordApp) | Out-Null return $true } catch { return $false } } # Checkif 7zip is installed to read rar,7z protected file by password or encrypted function Is-7zip { $sevenZipRegPath = "HKLM:\SOFTWARE\7-Zip" $sevenZipPath = Get-ItemProperty -Path $sevenZipRegPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path if (-not $sevenZipPath) { $sevenZipRegPath = "HKCU:\Software\7-Zip" #Check in HKCU $sevenZipPath = Get-ItemProperty -Path $sevenZipRegPath -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path } if ($sevenZipPath) { return $sevenZipPath } else { return $false } } $wordinstalled = Is-WordInstalled $zipinstalled = Is-7zip # Removing ghost runspaces $PreviousRS = get-runspace | where-object {($_.id -ne 1)} if ($PreviousRS) { $PreviousRS.dispose() } $Results = $null # Script start: obtain the current date and time $startDate = Get-Date #region getxmlcontenant # Retrieve file extensions from the XML file $xmlFileExtensions = Join-Path -Path $PSScriptRoot -ChildPath "file_extensions.xml" $extensionsXML = [xml](Get-Content $xmlFileExtensions -Encoding UTF8) if (!$Allextensions) { $fileExtensions = $extensionsXML.root.FileExtensions.Extension } else { $fileExtensions = "*.*" } if ($AddExtensions) { $fileExtensions = [System.Collections.ArrayList]@($fileExtensions) $AddExtensions | ForEach-Object { $fileExtensions.Add("*." + $_) } } # Retrieve password patterns from the XML file if ($Custompatterns) { try { $CustomextensionsXML = [xml](Get-Content $Custompatterns -Encoding UTF8) $passwordPatterns = $CustomextensionsXML.root.PasswordPatterns.Pattern } catch { Write-Error "Error while reading the custom patterns file: $_" throw "The custom patterns file could not be found or is invalid." } } else { $passwordPatterns = $extensionsXML.root.PasswordPatterns.Pattern } if ($passwordPatterns.Count -eq 0) { throw "The custom patterns file could not be found or is invalid." } if ($removepattern) { $passwordPatterns = [System.Collections.ArrayList]@($passwordPatterns) $PatternAliases = @{ ipv4 = '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' email = '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' MD5 = '\b[a-fA-F0-9]{32}\b' Sha1 = '\b[a-fA-F0-9]{40}\b' Sha256 = '\b[a-fA-F0-9]{64}\b' } # remplace Alias by real regex $removepattern | ForEach-Object { if ($PatternAliases.ContainsKey($_)) { #$PatternAliases[$_] $passwordPatterns.Remove($PatternAliases[$_].trim()) } } $removepattern | ForEach-Object { $patternToRemove = $_ $patternsToRemove = $passwordPatterns | Where-Object { $_ -like "*$patternToRemove*" } foreach ($pattern in $patternsToRemove) { $passwordPatterns.Remove($pattern) } } } if ($Addpattern) { $passwordPatterns = [System.Collections.ArrayList]@($passwordPatterns) $Addpattern | ForEach-Object { $passwordPatterns.Add($_) } } if ($ignoreExtensions) { $ignoreExtensions = $ignoreExtensions | ForEach-Object { "*." + $_ } } #get binary sign from json and load module path $module = Join-Path -Path $PSScriptRoot\Privates -ChildPath "FileHandlers.psm1" $jsonfile = Join-Path -Path $PSScriptRoot -ChildPath "extensions.json" $htmlfunction = Join-Path -Path $PSScriptRoot\Privates -ChildPath "Functioncheck.psm1" try { $jsonContent = Get-Content -Path $jsonfile -Raw | ConvertFrom-Json } catch { throw "Script stopped due to an error during json file $_ " } # Initialize a list to store the results $Results = @() # Informations for check ACL in sysvol # Get base SID $baseSID = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value -replace '-\d+$','' # Default accounts sid $sids = @( 'S-1-5-32-544', # Administrators 'S-1-5-18', # SYSTEM 'S-1-3-0', # CREATOR 'S-1-5-32-549', # Server Operator "$baseSID-512", # Domain Admins "$baseSID-519", # Entreprise Admins "$baseSID-520", # Creator GPO 'S-1-5-9' # Enterprise DCs ) # Trying get list default ACL users in sysvol $Trustgroups = $sids | ForEach-Object { $sid = New-Object System.Security.Principal.SecurityIdentifier($_) try { $sid.Translate([System.Security.Principal.NTAccount]).Value } catch { $sid.value # $sid.Value Check SID if not resolve by machine } } # Permission Trust $Permissiontrust = @( '-1610612736' 'ReadAndExecute', 'Read', 'Synchronize', 'ReadAndExecute, Synchronize', 'Read, Synchronize', 'ReadAttributes, ReadExtendedAttributes, ReadPermissions, Synchronize' ) #endregion getxmlcontenant #Initialize the variables $notAccessibleFiles = $fichiertraite = $Results = $null $pool = $runspaces = $null # Pool definition (creation of slots) $pool = [RunspaceFactory]::CreateRunspacePool(1,10) $pool.ApartmentState = "MTA" $pool.Open() $runspaces = @() #endregion load prerequist # Region Scriptfunction $scriptblock = { Param ( [object[]]$sysfiles1, [string[]]$passwordPatterns, [string]$wordinstalled, [string]$zipinstalled, [string]$module, [int]$Maxfilesize, [int]$MaxBinarysize, [object]$jsonContent, [string[]]$Trustgroups, [string[]]$Permissiontrust ) # Import modul FileHandlers.psm1 Import-Module $module -Force -Verbose $sysfiles = $sysfiles1.fullname $aclResults = Check-SysvolAcl -FilePath $sysfiles -TrustGroups $Trustgroups -PermissionTrust $Permissiontrust if ($aclResults) { $aclResults = $aclResults | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Name Created -Value $sysfiles1.CreationTime -Force $_ } return $aclResults } [String]$detectedType = Get-FileType -filePath $sysfiles -jsonContent $jsonContent -maxfilesize $Maxfilesize -maxbinarysize $MaxBinarysize # Function to search pattern by extensions switch ($detectedType) { 'docx' { $results = Get-DocxContent -filePath $sysfiles -patterns $passwordPatterns } 'xlsx' { $results = Get-XlsxContent -filepath $sysfiles -patterns $passwordPatterns } {$_ -in "xlsm","xlam"} { $results = Get-Xlsmcontent -filepath $sysfiles } 'pptx' { $results = Get-PPTContent -filepath $sysfiles -patterns $passwordPatterns -wordinstalled $wordinstalled } 'doc' { $results = Get-DocContent -filepath $sysfiles -patterns $passwordPatterns -wordinstalled $wordinstalled } 'xls' { $results = Get-XlsContent -filepath $sysfiles -patterns $passwordPatterns -wordinstalled $wordinstalled } {$_ -in "odp","ods","odt"} { $results = Get-OdsContent -filepath $sysfiles -patterns $passwordPatterns # Même traitement que ods } 'pdf' { $results = Get-PdfContent -filepath $sysfiles -patterns $passwordPatterns } 'xml' { $results = Get-XmlContent -filepath $sysfiles -patterns $passwordPatterns } {$_ -in "exe","dll","msi","msu","cab"} { $results = Get-ExecutablesContent -filepath $sysfiles } {$_ -in "cer","crt","cert","der","pfx","pem"} { $results = Get-CertifContent -filepath $sysfiles } {$_ -in "p7b","p7c","p7s"} { $results = Get-P7bCertContent -filepath $sysfiles } {$_ -in "bmp","webp","ico","bmp","tif"} { $results = Get-HiddenFilesInImage -filepath $sysfiles } {$_ -in "jpg","jpeg","png","gif"} { $results = Get-HiddenFilesSpecificInImage -filepath $sysfiles } {$_ -in "zip","7z","rar"} { $results = Get-Zipprotectedbypass -filepath $sysfiles -zipinstalled $zipinstalled } 'requires_check' { $results = Get-RequiredCheckContent -filepath $sysfiles } 'others' { $results = Get-OthersContent -filepath $sysfiles -patterns $passwordPatterns } 'bigsize' { $results = Get-checkfilesize -filepath $sysfiles } 'DefaultGPO' { $results = Get-DefaultGPOContent -filepath $sysfiles -patterns $passwordPatterns } } # Execute the appropriate command based on the detected file type if ($results) { $results | Add-Member -MemberType NoteProperty -Name Created -Value $sysfiles1.CreationTime -Force return $results } } if (Is-WordInstalled) { # Terminate the running Word and Excel processes to prevent double opening Get-process *winword* -erroraction SilentlyContinue | Stop-Process Get-Process excel -erroraction SilentlyContinue | Stop-Process } $fichiertraite = 0 # Define the array to store inaccessible files #[System.Collections.Generic.List[Object]]$notAccessibleFiles = @() $notAccessibleFiles = [System.Collections.Generic.List[object]]::new() function Test-SysvolRootStructure { param( [string]$Sysvolroot ) #$sysvolRoot = "\\$DomainName\SYSVOL" Write-Host "`n Found SYSVOL root: $sysvolRoot" -ForegroundColor Cyan $rootContent = Get-ChildItem -Path $sysvolRoot -Directory -Force -ErrorAction SilentlyContinue if (-not $rootContent) { Write-Warning "No folders found at the SYSVOL root. Check permissions or connectivity." return } $table = $rootContent | Sort-Object LastWriteTime | ForEach-Object { [PSCustomObject]@{ Name = $_.Name Type = if ($_.Attributes -match 'ReparsePoint') { "Linked (DFS)" } else { "Directory" } LastWriteTime = $_.LastWriteTime.ToString("yyyy-MM-dd HH:mm") } } $table | Format-Table -AutoSize | Out-String | Write-Host # Unexpected folders $expected = @("scripts", "Policies", "$DomainName") $unexpected = $rootContent | Where-Object { $_.Name -notin $expected } $Trustpath = ($rootContent | ? { $_.name -eq $dnsDomain}).fullname if ($unexpected) { Write-Warning "Unexpected folders detected in SYSVOL root:" foreach ($folder in $unexpected) { Write-Host " - $($folder.FullName)" -ForegroundColor Red $notAccessibleFiles.Add([PSCustomObject]@{ Error = "Unexpected folder found in SYSVOL: $($folder.FullName)" }) } } else { #Write-Host "`n SYSVOL root structure looks normal." -ForegroundColor Green } return $Trustpath } #$sysvolRoot = "\\$DomainName\SYSVOL" $sysvolpath = Test-SysvolRootStructure -Sysvolroot $sysvolRoot Start-Sleep -Seconds 2 clear Show-HardenSysvolBanner -BannerColor $Bannercolor Write-Host "[Step 1/4] Search files in $sysvolpath ..." -ForegroundColor $MSGcolor if (-not $IncludePolicyDefinitions) { $Allfiles = Get-ChildItem -Path $sysvolpath -Recurse -File -Include $fileExtensions -Exclude $ignoreExtensions -Force -ErrorAction SilentlyContinue -ErrorVariable notacess | Where-Object { $_.FullName -notmatch '\\Policies\\PolicyDefinitions\\' } } else { $Allfiles = Get-ChildItem -Path $sysvolpath -Recurse -File -Include $fileExtensions -Exclude $ignoreExtensions -Force -ErrorAction SilentlyContinue -ErrorVariable notacess } Write-Host "Found $($Allfiles.Count) file(s) to analyze." -ForegroundColor $MSGcolor Write-Host "[Step 2/4] Starting scan ..." -ForegroundColor $MSGcolor $Allfiles | ForEach-Object { if ($notacess) { #Write-Host $notacess -ForegroundColor Red $notacess.GetEnumerator() | ForEach-Object { $errorDetails = [PSCustomObject]@{ Error = $_ } } $notAccessibleFiles.Add($errorDetails) $notacess.Clear() } else { $fichiertraite++ $sysfiles1 = [PSCustomObject]@{ FullName = $_.FullName CreationTime = ($_.CreationTime.ToString("yyyy-MM-dd")) } #clear #Write-Host scan : $sysfiles1.FullName -ForegroundColor Cyan $keepscrenn += "scans :" + $sysfiles1.FullName $runspace = [PowerShell]::Create() $null = $runspace.AddScript($scriptblock) $null = $runspace.AddArgument($sysfiles1) $null = $runspace.AddArgument($passwordPatterns) $null = $runspace.AddArgument($wordinstalled) $null = $runspace.AddArgument($zipinstalled) $null = $runspace.AddArgument($module) $null = $runspace.AddArgument($Maxfilesize) $null = $runspace.AddArgument($MaxBinarysize) $null = $runspace.AddArgument($jsonContent) $null = $runspace.AddArgument($Trustgroups) $null = $runspace.AddArgument($Permissiontrust) $runspace.RunspacePool = $pool $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() } } } Write-Host "[Step 3/4] Starting analyze ..." -ForegroundColor $MSGcolor function Show-CustomProgressBar { param ( [int]$Completed, [int]$Total, [TimeSpan]$Elapsed ) $percent = if ($Total -gt 0) { [math]::Round(($Completed / $Total) * 100) } else { 0 } $barLength = 30 $filledLength = [math]::Round(($percent / 100) * $barLength) $bar = ('#' * $filledLength).PadRight($barLength, '.') Write-Host "`rProcessed $Completed in $Total files [$bar] $percent% | Elapsed : $($elapsed.ToString("hh\:mm\:ss")) " -ForegroundColor Cyan -NoNewline } $timer = [System.Diagnostics.Stopwatch]::StartNew() $total = $runspaces.Count while ($runspaces.Status -ne $null) { Start-Sleep -Seconds 1 $completed = @() $slt_remaining = 0 foreach ($r in $runspaces) { if ($r.Status -ne $null) { if ($r.Status.IsCompleted) { $completed += $r } else { $slt_remaining++ } } } $slt_completed = $total - $slt_remaining $elapsed = $timer.Elapsed $percent = [math]::Round(($slt_completed / $total) * 100) Show-CustomProgressBar -Completed $slt_completed -Total $total -Elapsed $elapsed foreach ($runspace in $completed) { $Results += $runspace.Pipe.EndInvoke($runspace.Status) $runspace.Status = $null } if (($runspaces | Where-Object { $_.Status -ne $null }).Count -eq 0) { Write-Progress -Activity "Scan completed" -Completed break } } #endregion code #region summary Write-Host "`n[Step 3/4] Scan completed, calculating statistics ..." -ForegroundColor $MSGcolor # Sort results in unique mode and Skip default GPO policy password settings $Results = $Results | Select-Object -Unique FilePath, pattern, Reason, Created $sortedGroups = $Results.filepath | Group-Object | Sort-Object -Property Count -Descending # Select the first 5 Path $top5path = $sortedGroups | Select-Object Name,Count -First 5 # Remove commun domain path #$commonPath = "\\$dnsDomain\sysvol\$dnsDomain" $commonPath = "$sysvolRoot\$dnsDomain" $top5path = $top5path | ForEach-Object { $_.Name = $_.Name -replace '^.*\\', '' $_ } # Top 5 words $top5Words= $Results.pattern | Group-Object | Sort-Object -Property Count -Descending | Select-Object Name,count -First 5 $Allwords = $Results | Group-Object -Property Pattern | Sort-Object -Property Count -Descending | Select-Object Name,Count #Number found objects $Objectfound = 0 $Objectfound = $sortedGroups.Count # Group file paths by file extension $groupedFiles = $sortedGroups.name | Group-Object -Property { ($_ -split "\.")[-1] } | select Name,Count # End of the script: obtain the current date and time $endDate = Get-Date # Calculate the time difference $elapsedTime = New-TimeSpan -Start $startDate -End $endDate $elapsedTime = $($elapsedTime.ToString("hh\:mm\:ss")) #region Calcul potentiel risk # Assume that $top5Groups, $Allwords, and $Objectfound are already defined" $totalRisk = 0 # Assess the risk based on the number of files containing passwords if ($Objectfound -gt 30) { $totalRisk += 10 } else { $totalRisk += ($Objectfound/30) * 10 } # Iterate through the keywords in $Allwords and adjust the risk score foreach ($word in $Allwords) { switch -Regex ($word.Name) { "AutoLogon|cpassword" { $totalRisk += 7 * $word.Count break } "Password|Pass|\bpass\b|\bpwd\b" { $totalRisk += 3 * $word.Count break } "net use|net user|NotSigned|check required" { $totalRisk += 3 * $word.Count break } "sha1|md5" { $totalRisk += 5 * $word.Count break } "credentials|\bsecret\b|IPv4" { $totalRisk += 2 * $word.Count break } } } # Limited score to 100% if ($totalRisk -gt 100) { $totalRisk = 100 } else { $totalRisk = [int]$totalRisk } $ResultsRenamed = $Results | Select-Object ` @{Name = 'FilePath'; Expression = { $_.FilePath }}, @{Name = 'Reason' ; Expression = { $_.Pattern }}, @{Name = 'Value'; Expression = { $_.Reason }}, @{Name = 'Created'; Expression = { $_.Created }} #endregion Calcul potentiel risk #endregion Summary # Close all Slots and pool $pool.Close() $pool.Dispose() #endregion Script Import-Module $htmlfunction -Force if ($CheckShares) { Write-Host "Check Share on Domain controllers" -ForegroundColor Cyan $SafeRights = @( 'Read','ReadAndExecute', 'Synchronize','ReadAttributes', 'ReadExtendedAttributes','ReadPermissions', 'AppendData','ReadData','ExecuteFile', '-1610612736','Synchronize' ) $ShareonDC = @() $listOfDCs = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers ForEach ($DC in $listOfDCs) { Write-Host "Check Shares on $DC" -ForegroundColor Cyan if (Test-Path "\\$DC\SYSVOL") { try { $Shares = Get-RemoteDiskShares -ComputerName $DC foreach ($share in $Shares) { $SharePath = "\\$DC\$($share.Name)" if (Test-Path $SharePath) { $Findings = Get-ShareACL -FilePath $SharePath -TrustGroups $Trustgroups -PermissionTrust $SafeRights if ($Findings) { foreach ($finding in $Findings) { $ShareonDC += [PSCustomObject]@{ DC = $DC Share = $share.Name Type = $share.Type ACL = $finding.Pattern Reason = $finding.Reason Comment = $share.Comment } } } else { $ShareonDC += [PSCustomObject]@{ DC = $DC Share = $share.Name Type = $share.Type ACL = 'Clean' Reason = '-' Comment = $share.Comment } } } else { $ShareonDC += [PSCustomObject]@{ DC = $DC Share = $share.Name Type = $share.Type ACL = 'Inaccessible' Reason = 'Test-Path failed' Comment = $share.Comment } } } } catch { Write-Warning "Erreur sur $DC : $_" } } else { $notAccessibleFiles.Add([PSCustomObject]@{ Error = "SYSVOL is not accessible on $DC" }) } } } #region HTML if ($Exportcsv) { $Results | Export-Csv -Path $Exportcsv } else { #Option Offline, without open nav for hosting on webserver if ($Silent.IsPresent) { $htmlvalue = $false } else { $htmlvalue = $true } $logo = "https://github.com/dakhama-mehdi/Harden-Sysvol/blob/main/Pictures/HardenSysvol.png?raw=true" $rightlogo = "https://github.com/dakhama-mehdi/Harden-Sysvol/blob/main/Pictures/Rightlogo.png?raw=true" $totalfiles = $runspaces.count # Generate HTML report Write-Host "[Step 4/4] Generate HTML ..." -ForegroundColor $MSGcolor Write-Host "Resume : Founds $Objectfound in $totalfiles" -ForegroundColor Cyan #Option Offline, without open nav for hosting on webserver if ($Silent.IsPresent) { $htmlvalue = $false [string]$SavePath = $SavePath + "\hardensysvol.html" } else { [string]$date = Get-Date -Format "yyMMdd_HHmm" [string]$SavePath = $SavePath + "\hardensysvol_$date.html" $htmlvalue = $true } New-HTML -TitleText 'HardenSysvol' -FilePath $SavePath -ShowHTML:$htmlvalue { New-HTMLHeader { New-HTMLSection -Invisible -Margin "0px 25px" { New-HTMLPanel -Invisible -AlignContentText left { New-HTMLImage -Source $logo -Class 'otehr' -Width 30% -Height 20% } New-HTMLPanel -Invisible -AlignContentText right { New-HTMLText -LineBreak New-HTMLText -Text "Report date: $startDate" -Alignment right -FontSize 15 } } } New-HTMLTab -Name '📊 Dashboard' { New-HTMLTabStyle -BackgroundColorActive '#004c87' New-HTMLSection -Invisible -Density Compact -Margin "0px 25px" { New-HTMLInfoCard -Title "Domain" -Number $dnsDomain -Style Compact -Alignment Center -BackgroundColor Lavender New-HTMLInfoCard -Title "Scanned Files" -Number $totalfiles -Style Compact -Alignment Center -BackgroundColor Thistle New-HTMLInfoCard -Title "Suspicious Files" -Number $Objectfound -Style Compact -Alignment Center -BackgroundColor Lavender New-HTMLInfoCard -Title "Elapsed Time" -Number $elapsedTime -Style Compact -Alignment Center -BackgroundColor Thistle } New-HTMLSection -name 'Summary' -Margin "0px 25px" -HeaderBackGroundColor '#004c87' { New-HTMLPanel { New-HTMLGaugeLight -value $totalRisk -Title "Indicator Risk : $totalRisk" } New-HTMLPanel { New-HTMLTableOption -DataStore HTML #-ArrayJoin -ArrayJoinString ',' -PrettifyObject New-HTMLTable -DataTable $top5path -HideFooter -HideButtons -HideShowButton -DisablePaging -DisableSearch { if ($top5path) { New-TableHeader -Names 'Name', 'Count' -Title 'Top 5 Files' -Alignment center -BackGroundColor LightSteelBlue } } } New-HTMLPanel { New-HTMLTable -DataTable $top5Words -HideFooter -HideButtons -HideShowButton -DisablePaging -DisableSearch { if ($top5Words) { New-TableHeader -Names 'name', 'count' -Title 'Top 5 Reason' -Alignment center -BackGroundColor LightSteelBlue } } } New-HTMLPanel { New-HTMLList { New-HTMLListItem -Text 'Harden-Sysvol Version : 2.2.5' New-HTMLListItem -Text 'Release : 01/2026' New-HTMLListItem -Text 'Author : Dakhama Mehdi<br> <br> Credit : HardenAD Community [HardenAD](https://www.hardenad.net/) <br> Credit : It-connect Community [It-Connect](https://www.it-connect.fr/) <br> Thanks : Przemyslaw Klys [Evotec](https://evotec.xyz) for Module PSWriteHTML/PswriteOffice' } -FontSize 14 } } New-HTMLSection -Invisible -HeaderBackGroundColor '#004c87' -Margin "10px 25px" { New-HTMLPanel { New-HTMLTable -DataTable $groupedFiles -HideFooter -HideButtons -HideShowButton -PagingLength 5 -DisableSearch -DefaultSortColumn Count -DefaultSortOrder Descending { if ($groupedFiles) { New-HTMLTableHeader -Names 'name', 'count' -Title 'Types of Extensions found' -Alignment center -BackGroundColor '#004c87' -Color White } } } New-HTMLPanel { New-HTMLImage -Source $rightlogo } } } New-HTMLTab -Name '🔍 Details' { New-HTMLSection -Margin "0px 25px" -Invisible -HeaderBackGroundColor '#004c87' { New-HTMLTableOption -DataStore HTML -ArrayJoin -ArrayJoinString ',' -PrettifyObject New-HTMLTable -DataTable $ResultsRenamed -PagingLength 10 -HideShowButton -DisableAutoWidthOptimization -Buttons copyHtml5,csvHtml5,pdfHtml5,searchBuilder -HideFooter -Style cell-border { if ($ResultsRenamed) { New-TableCondition -ColumnName 'Reason' -ComparisonType string -Operator 'eq' -Value 'WrongACL' -Color 'Red' -Row $headers = $ResultsRenamed[0].PSObject.Properties.Name #New-HTMLTableColumnOption -ColumnIndex 0 -Width 200px New-HTMLTableContent -WordBreak break-word -ColumnName 'FilePath' #New-HTMLTableColumnOption -ColumnIndex 2 -Width 300px New-HTMLTableContent -WordBreak break-word -ColumnName 'Value' foreach ($head in $headers) { New-HTMLTableHeader -Names $head -Color White -BackGroundColor '#004c87' } } } } if ($CheckShares) { New-HTMLSection -Invisible -Margin "10px 25px" { New-HTMLTableOption -DataStore HTML New-htmlTable -DataTable $ShareonDC -TextWhenNoData 'No Share suspect found' -HideFooter -HideButtons -HideShowButton -DisableSearch { if ($ShareonDC) { New-TableHeader -Names 'DC', 'Share','Type','ACL','Reason','Comment' -Title 'Suspicious Shares' -Alignment center -BackGroundColor DarkGreen -Color White } } } } New-HTMLSection -Invisible -Margin "10px 25px" { New-HTMLTableOption -DataStore HTML -PrettifyObject New-htmlTable -DataTable $notAccessibleFiles -TextWhenNoData 'No errors during scanning' -HideFooter -HideButtons -HideShowButton -DisablePaging -DisableSearch { if ($notAccessibleFiles) { New-TableHeader -Names 'Error' -Title 'Errors logs' -Alignment center -BackGroundColor Coral -Color White } } } } } } #endregion HTML } # SIG # Begin signature block # MIItjQYJKoZIhvcNAQcCoIItfjCCLXoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD7Kdu9DKFpxvId # nUgIp8cNtGUVn6/mXyy1KQ+0c0tqk6CCEtUwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZHMIIEL6ADAgECAhA12OBytW+cTayv # VHUpRhwLMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNTExMTYxMTAwMTlaFw0yNjExMTYxMTAwMThaMG0x # CzAJBgNVBAYTAkZSMQ8wDQYDVQQHDAZUb3Vsb24xHjAcBgNVBAoMFU9wZW4gU291 # cmNlIERldmVsb3BlcjEtMCsGA1UEAwwkT3BlbiBTb3VyY2UgRGV2ZWxvcGVyLCBE # QUtIQU1BIE1FSERJMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAp6Ku # m/VmkWCqAaF/3zHh9f1FuJYY2ozbXOu7mo1/Q8i1c0fE0TXpkZXLY2GZbfpj9BmH # AAFM0IhOsPR2vdxq3jOUJUb9TICneFor6YaPpySsXR3WSE7X42kgpkkmPELovm1Y # hwSzhJ4a+E+NWL/MU8h5JpmGVlqPJ02/ZTlMj5kcpIQtq8hoQMcUEDkGFt9IcamE # 1yN4IHkBA5nm4jJPaos0IuS77t805992JSGWhxBxWARH+2vyltv8Rmq1pZV1lE6n # JgrWT7Ichjw2X/A+OP68ooTzQwCIpzXb4UuUcwHEfrmP3HGMQJoj//SNC4QPMao+ # 3Z8zbevl73E3d6Kfvra1S+pWM2Ze5YCsIqAd98GUHgi5E6GiG8FQq/+d6msL7l8B # UASCqXlcAKIjRNMHp8BrUaaW6HS9Kpc+3O3t/LUmK6X3FFiW8QsWoh4K+7YSpopa # CQbNXmEI4xftctwBOJrEU2oqRnYiwchfjqBNlrGwVGPK1rmM0iTt5KiLTus7AgMB # AAGjggF4MIIBdDAMBgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6 # Ly9jY3NjYTIwMjEuY3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUF # BwEBBGcwZTAsBggrBgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1 # bS5jb20wNQYIKwYBBQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9j # Y3NjYTIwMjEuY2VyMB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0G # A1UdDgQWBBSXTmfHi9BD9GDRwk5/doNtKHBXYzBLBgNVHSAERDBCMAgGBmeBDAEE # ATA2BgsqhGgBhvZ3AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0 # dW0ucGwvQ1BTMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDAN # BgkqhkiG9w0BAQsFAAOCAgEAe+khGqwUUkFYuFRsrvenX2/a+PIt2Tu9d3VoW6Or # MX3YLpe7S2CgFkXwEi2Siq5KiD1labP9jsh/3G1ZQwwlnPv8dB7ocl/nOrQ9OZex # GVE1r7IO6VYVa5F7XuJ/KadKLEbQSs1BpBVhESo1ZYr6w9NCLuO9q2Sh3H5MktET # D6sB+g1TFOYMdwYl8eAawgI2kGPe3dRQSoumP0mHkm3x5SIwRCW+08md5uyzCIui # 85WmcNPtM1QCqjkSpfdFGYPsnf/BO9NATpZkqFxhXwa9+PqseX+mofCIL49guCXG # kU4RpeRHcUie14oYkxvBw7VUO4MT6wYbS2C3j2nyoAV4XqqNMfrhZIBJG5haj2RB # V46bMJ+DsW6hxlm3lIlCaJT2pLbbk79OP+Bk0HIdC9mAbKzcqaZpBpn4+ljrcx7/ # X7OHv4XTCCDWwlZbaogy4Wci6TiSjjfpfXK5N/eJTEEh2w4qoYTTrR61ptkVnTUT # vGRfPnVtS/3aOm2v4UahtOc/ygcL0A/J85r1e6CEeOaTm9eJbHoNdwNIYaZ81VlX # /V/MoJgFCtioYOKiTf2Rdq7XrEEHLU2YGwCqJyKYz9tz10yXBcMW6/+gX+PGqAYz # eKg5jbKLdi9lVrKspQUXAPHdcl6VJMXy799J0lbsQeJNgBVy6HWxOWvdLBGX3hPE # 3aYwgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6EprPqOTANBgkqhkiG9w0BAQwF # ADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVz # IFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEk # MCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1 # MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz # c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln # bmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnSPP # BDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4mSAfgLKTvggv24/rWiVGzGxT # 9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck6+hI1shsylP4JyLvmxwLHtSw # orV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xqbNTIPsnWtw3E7DmDoUmDQiYi # /ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KNqfcYf7Z4/iZgkBJ+UFNDcc6z # okZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSlbfsq7BiqljjaCun+RJgTgFRC # tsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYqVWQ4x6cM7/G0yswg1ElLlJj6 # NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAExzd4prHwYjUXTeZIlVXqj+ea # YqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962WZayx7ACFf5ydJpoEowSP07Y # aBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiYPIAHlezf4muJqxqIns/kqld6 # JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36E/ENVv8urPS0yOnpG4tIoBGx # VCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPECAwEAAaOCAVUwggFRMA8GA1Ud # EwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23ufoHTKsW73PMAywHDNMB8GA1Ud # IwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNV # HSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNl # cnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYc # aHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDov # L3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRV # HSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkq # hkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP89DttyZqGYn2XRDhJkL6P+/T0 # IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3vjzzH8Jq3iR74BRlkO18J3zId # mCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO0eqeLNhLVsgw6/zOfImNlARK # n1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2mbJCKKtl2s42g9KUJHEIiLni # 9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA3ahe8WlxVWkxyrQLjH8ItI17 # RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhpn8zJ+6MTyE6YoEBSRVrs0zFF # IHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSouCReZwSLo8WdrDlPXtR0gicD # nytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na3PR7nxYVlPu1JPoRZCbH86gc # 96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1eEJfo2qyrBNPvF7wuAyQfiFX # LwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+tKyEVAotnyU9vyEVOaIYMk3I # eBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmvO1nm+/80nLy5r0AZvCQxaQ4x # ghoOMIIaCgIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAy # MSBDQQIQNdjgcrVvnE2sr1R1KUYcCzANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB # gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC # AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAqtE0KNS+fIcEcTrPo # Bypv4d329RFsC7hds4zEfARw7TANBgkqhkiG9w0BAQEFAASCAYAHeSevKToZx/rw # PbwNTmZmx2jx+4DiEhBU48LeihcknBvn5a2zXGrtK7arhIosgz9KKRQMN/+f978P # NI7HUXeHHnV0YoG0Y+vkgw6qK6a3NvWRzF9fTAXlce2u3dSTg2P1+1IkVU8IQb2g # oCwWCO9+uhBF5XCydxnvo4WOJsyeDZcdk3j120bKG6Or59QZWrJavmT67R2HYlwI # bLJYbBIRAC0VOns4jbzTNPf7qAjT39vhNUN7Z57BAfcx79HIRrR024OmZGMGn/xA # 6JCH8obnLDnZ94gHap8h9OTEee/NStw+/99m2lVZ778o9AHnbA0P8AgvQN7565fK # jpdMmuhPI8PRfbGXscNb3y6MsNcmwCIBXBsVwFemCLYESjSIeFfKr4k20DS/vwI2 # mRP7ruU+fjOUpV25shvHYNnu9hZyBy1LFGQzMP3dEwJkChqNVHuSy0RZ0yI0DK7s # nduU+nYdXToq8VwXC1LvOjwDj4guonkVvwyrUwoD1J7IDzlyzHmhghd3MIIXcwYK # KwYBBAGCNwMDATGCF2MwghdfBgkqhkiG9w0BBwKgghdQMIIXTAIBAzEPMA0GCWCG # SAFlAwQCAQUAMHgGCyqGSIb3DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTAN # BglghkgBZQMEAgEFAAQgkGbgR7n5+A5LgxWwiT3/6Wpw5r2OpA0dT7kZri3wWeYC # EQCe2Lm3syYmM9GHWx6fj452GA8yMDI2MDEyMjE2MTAwMVqgghM6MIIG7TCCBNWg # AwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3 # zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8Tch # TySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWj # FDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2Uo # yrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjP # KHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KS # uNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7w # JNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vW # doUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOg # rY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K # 096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCf # gPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zy # Me39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZ # D9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ # ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu # +WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4o # bEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2h # ECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasn # M9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol # /DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgY # xQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3oc # CVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcB # ZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCBrQwggSc # oAMCAQICEA3HrFcF/yGZLkBDIgw6SYYwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTI1 # MDUwNzAwMDAwMFoXDTM4MDExNDIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBALR4MdMKmEFyvjxGwBysddujRmh0tFEXnU2t # jQ2UtZmWgyxU7UNqEY81FzJsQqr5G7A6c+Gh/qm8Xi4aPCOo2N8S9SLrC6Kbltqn # 7SWCWgzbNfiR+2fkHUiljNOqnIVD/gG3SYDEAd4dg2dDGpeZGKe+42DFUF0mR/vt # La4+gKPsYfwEu7EEbkC9+0F2w4QJLVSTEG8yAR2CQWIM1iI5PHg62IVwxKSpO0Xa # F9DPfNBKS7Zazch8NF5vp7eaZ2CVNxpqumzTCNSOxm+SAWSuIr21Qomb+zzQWKhx # KTVVgtmUPAW35xUUFREmDrMxSNlr/NsJyUXzdtFUUt4aS4CEeIY8y9IaaGBpPNXK # FifinT7zL2gdFpBP9qh8SdLnEut/GcalNeJQ55IuwnKCgs+nrpuQNfVmUB5KlCX3 # ZA4x5HHKS+rqBvKWxdCyQEEGcbLe1b8Aw4wJkhU1JrPsFfxW1gaou30yZ46t4Y9F # 20HHfIY4/6vHespYMQmUiote8ladjS/nJ0+k6MvqzfpzPDOy5y6gqztiT96Fv/9b # H7mQyogxG9QEPHrPV6/7umw052AkyiLA6tQbZl1KhBtTasySkuJDpsZGKdlsjg4u # 70EwgWbVRSX1Wd4+zoFpp4Ra+MlKM2baoD6x0VR4RjSpWM8o5a6D8bpfm4CLKczs # G7ZrIGNTAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW # BBTvb1NK6eQGfHrK4pBW9i/USezLTjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/ # 57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYI # KwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1Ud # IAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEA # F877FoAc/gc9EXZxML2+C8i1NKZ/zdCHxYgaMH9Pw5tcBnPw6O6FTGNpoV2V4wzS # UGvI9NAzaoQk97frPBtIj+ZLzdp+yXdhOP4hCFATuNT+ReOPK0mCefSG+tXqGpYZ # 3essBS3q8nL2UwM+NMvEuBd/2vmdYxDCvwzJv2sRUoKEfJ+nN57mQfQXwcAEGCvR # R2qKtntujB71WPYAgwPyWLKu6RnaID/B0ba2H3LUiwDRAXx1Neq9ydOal95CHfmT # nM4I+ZI2rVQfjXQA1WSjjf4J2a7jLzWGNqNX+DF0SQzHU0pTi4dBwp9nEC8EAqox # W6q17r0z0noDjs6+BFo+z7bKSBwZXTRNivYuve3L2oiKNqetRHdqfMTCW/NmKLJ9 # M+MtucVGyOxiDf06VXxyKkOirv6o02OoXN4bFzK0vlNMsvhlqgF2puE6FndlENSm # E+9JGYxOGLS/D284NHNboDGcmWXfwXRy4kbu4QFhOm0xJuF2EZAOk5eCkhSxZON3 # rGlHqhpB/8MluDezooIs8CVnrpHMiD2wL40mm53+/j7tFaxYKIqL0Q4ssd8xHZnI # n/7GELH3IdvG2XlM9q7WP/UwgOkw/HQtyRN62JK4S1C8uw3PdBunvAZapsiI5YKd # vlarEvf8EA+8hcpSM9LHJmyrxaFtoza2zNaQ9k+5t1wwggWNMIIEdaADAgECAhAO # mxiO+dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # JDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEw # MDAwMDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT # GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprN # rnsbhA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVy # r2iTcMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4 # IWGbNOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13j # rclPXuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4Q # kXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQn # vKFPObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu # 5tTvkpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/ # 8tWMcCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQp # JYls5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFf # xCBRa2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGj # ggE6MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/ # 57qYrhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8B # Af8EBAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6 # oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElE # Um9vdENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEB # AHCgv0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0a # FPQTSnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNE # m0Mh65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZq # aVSwuKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCs # WKAOQGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9Fc # rBjDTZ9ztwGpn1eqXijiuZQxggN8MIIDeAIBATB9MGkxCzAJBgNVBAYTAlVTMRcw # FQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3Rl # ZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhL # jfEFgtHEdqeVdGgwDQYJYIZIAWUDBAIBBQCggdEwGgYJKoZIhvcNAQkDMQ0GCyqG # SIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjAxMjIxNjEwMDFaMCsGCyqGSIb3 # DQEJEAIMMRwwGjAYMBYEFN1iMKyGCi0wa9o4sWh5UjAH+0F+MC8GCSqGSIb3DQEJ # BDEiBCCbJizwLaAniUUDYMKGkVGR0kV22PyWZt/TNPI6yTQbATA3BgsqhkiG9w0B # CRACLzEoMCYwJDAiBCBKoD+iLNdchMVck4+CjmdrnK7Ksz/jbSaaozTxRhEKMzAN # BgkqhkiG9w0BAQEFAASCAgBtuoUpL4DJblanUZlz6RXnV6rEFSkY5tknAh+ZWdEl # qmgausX23IoqxQaKWsqCKj2hk+SCorFHobHyV0IE50LYnlowmziKp4UXspx9kK7H # JJNUq1i+tlPomn2srFEEUk9K9+YFBx8aWXnY79vA/iLYtAbS1OSZd7TXe5pwgFHH # Vd3FsS5VyOMDb1nY/NmAZYZibru0ywnE9dPkwh8MDkMEPkhxlLYoGJXIQ9tdKteW # rrqH26Gu0PQuTW7coETVtKTCf8/jdgKNKSBsiuKpdxNyVhI57q8DqyoACDm1QZwi # Td3byeGP7YkEXsUZCwI9Ybeyw2Rgpnsam5vi9fZtM+SVNlfYVVnixFjTKIFvgXc5 # wVd1y+XH3XU/qThIfH7iHMeeQp0mhMspSzLNEZMfqR+SfvBXaoNhoM4TICbC/YTW # WUyEMGoV21qLRJWdsrSLlYfvF3iobKYeiosPx1uJ/4wdzXztGyIaCCenyE2hFm0u # Zu/9nV2R3mJawp98XQgu8WOJn2FaerLRgT3o8eU6KX5ZkKO3M3sCBoXX/HvSVL93 # TNqohJ6ak1sCBuq8P/rmKMdh3DbhSOYFLSy4M/Qk+2u7r2DcLGlB8peNdrkF+P7r # 7AxGZI5zN/LuW2246hQe5OEj1GfFWLztzNYyibUdJ9tsU+GJbwDudeSVRfQO26+G # mg== # SIG # End signature block |