From 410193847ec27d15ee957b46f28399b906fa77ee Mon Sep 17 00:00:00 2001 From: Jonathan Colon Date: Sun, 23 Feb 2025 23:14:46 -0400 Subject: [PATCH 1/7] Refactor Invoke-Command calls to use CAHostFQDN and improve connection handling. Fix Unable to scan Forest with child domains #237 --- Invoke-Locksmith.ps1 | 42 ++++++++++++++-------------- Private/Set-AdditionalCAProperty.ps1 | 8 +++--- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 783ec8c..a28a360 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -2900,11 +2900,11 @@ function Set-AdditionalCAProperty { $CAHostDistinguishedName = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Server $ForestGC ).DistinguishedName $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } - $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 + $ping = if ($CAHostFQDN) { Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet } else { Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" } if ($ping) { try { if ($Credential) { - $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName + $CertutilAudit = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg CA\AuditFilter } } else { $CertutilAudit = certutil -config $CAFullName -getreg CA\AuditFilter @@ -2915,7 +2915,7 @@ function Set-AdditionalCAProperty { } try { if ($Credential) { - $CertutilFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg policy\EditFlags } -ArgumentList $CAFullName + $CertutilFlag = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg policy\EditFlags } } else { $CertutilFlag = certutil -config $CAFullName -getreg policy\EditFlags @@ -2926,7 +2926,7 @@ function Set-AdditionalCAProperty { } try { if ($Credential) { - $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\InterfaceFlags } -ArgumentList $CAFullName + $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg CA\InterfaceFlags } } else { $CertutilInterfaceFlag = certutil -config $CAFullName -getreg CA\InterfaceFlags @@ -3395,23 +3395,23 @@ function Set-RiskRating { switch ($Issue.objectClass) { # Being able to modify Root CA Objects is very bad. 'certificationAuthority' { - $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' + $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' } # Being able to modify Issuing CA Objects is also very bad. 'pKIEnrollmentService' { - $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' + $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' } # Being able to modify CA Hosts? Yeah... very bad. 'computer' { - $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' + $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' } # Being able to modify OIDs could result in ESC13 vulns. 'msPKI-Enterprise-Oid' { - $RiskValue += 1; $RiskScoring += 'OID: +1' + $RiskValue += 1; $RiskScoring += 'OID: +1' } # Being able to modify PKS containers is bad. 'container' { - $RiskValue += 1; $RiskScoring += 'Container: +1' + $RiskValue += 1; $RiskScoring += 'Container: +1' } } } @@ -3420,19 +3420,19 @@ function Set-RiskRating { # Convert Value to Name $RiskName = switch ($RiskValue) { { $_ -le 1 } { - 'Informational' + 'Informational' } 2 { - 'Low' + 'Low' } 3 { - 'Medium' + 'Medium' } 4 { - 'High' + 'High' } { $_ -ge 5 } { - 'Critical' + 'Critical' } } @@ -3981,7 +3981,7 @@ Set-Acl -Path `$Path -AclObject `$ACL "@ } 4 { - break + break } 5 { $Issue.Fix = @" @@ -4144,10 +4144,10 @@ Function Write-HostColorized { # We precompile them for better performance with many input objects. [System.Text.RegularExpressions.RegexOptions] $reOpts = if ($CaseSensitive) { - 'Compiled, ExplicitCapture' + 'Compiled, ExplicitCapture' } else { - 'Compiled, ExplicitCapture, IgnoreCase' + 'Compiled, ExplicitCapture, IgnoreCase' } # Transform the dictionary: @@ -4169,10 +4169,10 @@ Function Write-HostColorized { } $colorArgs = @{ } if ($fg) { - $colorArgs['ForegroundColor'] = [ConsoleColor] $fg + $colorArgs['ForegroundColor'] = [ConsoleColor] $fg } if ($bg) { - $colorArgs['BackgroundColor'] = [ConsoleColor] $bg + $colorArgs['BackgroundColor'] = [ConsoleColor] $bg } # Consolidate the patterns into a single pattern with alternation ('|'), @@ -4191,7 +4191,7 @@ Function Write-HostColorized { } } catch { - throw + throw } # Construct the arguments to pass to Out-String. @@ -4214,7 +4214,7 @@ Function Write-HostColorized { foreach ($m in $entry.Key.Matches($_)) { @{ Index = $m.Index; Text = $m.Value; ColorArgs = $entry.Value } if ($WholeLine) { - break patternLoop + break patternLoop } } } diff --git a/Private/Set-AdditionalCAProperty.ps1 b/Private/Set-AdditionalCAProperty.ps1 index a4fe817..ca66874 100644 --- a/Private/Set-AdditionalCAProperty.ps1 +++ b/Private/Set-AdditionalCAProperty.ps1 @@ -127,11 +127,11 @@ $CAHostDistinguishedName = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Server $ForestGC ).DistinguishedName $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } - $ping = Test-Connection -ComputerName $CAHostFQDN -Quiet -Count 1 + $ping = if ($CAHostFQDN) { Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet } else { Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" } if ($ping) { try { if ($Credential) { - $CertutilAudit = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\AuditFilter } -ArgumentList $CAFullName + $CertutilAudit = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg CA\AuditFilter } } else { $CertutilAudit = certutil -config $CAFullName -getreg CA\AuditFilter } @@ -140,7 +140,7 @@ } try { if ($Credential) { - $CertutilFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg policy\EditFlags } -ArgumentList $CAFullName + $CertutilFlag = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg policy\EditFlags } } else { $CertutilFlag = certutil -config $CAFullName -getreg policy\EditFlags } @@ -149,7 +149,7 @@ } try { if ($Credential) { - $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostname -Credential $Credential -ScriptBlock { param($CAFullName); certutil -config $CAFullName -getreg CA\InterfaceFlags } -ArgumentList $CAFullName + $CertutilInterfaceFlag = Invoke-Command -ComputerName $CAHostFQDN -Credential $Credential -ScriptBlock { certutil -config $using:CAFullName -getreg CA\InterfaceFlags } } else { $CertutilInterfaceFlag = certutil -config $CAFullName -getreg CA\InterfaceFlags } From 2ee8c3d05fcbff77f27bdd849623aff7d08b9d26 Mon Sep 17 00:00:00 2001 From: Sam Erde Date: Mon, 3 Mar 2025 05:45:35 -0500 Subject: [PATCH 2/7] Update jinja to 3.1.5 --- Docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/requirements.txt b/Docs/requirements.txt index 10179c9..a384e05 100644 --- a/Docs/requirements.txt +++ b/Docs/requirements.txt @@ -1,6 +1,6 @@ # https://github.com/readthedocs-examples/example-mkdocs-basic/blob/main/docs/requirements.txt # requirements.txt -jinja2==3.1.4 #https://pypi.org/project/Jinja2/ +jinja2==3.1.5 #https://pypi.org/project/Jinja2/ mkdocs>=1.6.0 #https://github.com/mkdocs/mkdocs mkdocs-material==9.5.25 #https://github.com/squidfunk/mkdocs-material pygments>=2.18.0 #https://pypi.org/project/Pygments/ From 70bdf945b029f1c3195ad47d3aac4f466b0f5961 Mon Sep 17 00:00:00 2001 From: Sam Erde Date: Tue, 4 Mar 2025 15:28:35 -0500 Subject: [PATCH 3/7] Add more tags after comparing to related projects in Gallery --- Build/Build-Module.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/Build-Module.ps1 b/Build/Build-Module.ps1 index beb54db..1bf4d00 100644 --- a/Build/Build-Module.ps1 +++ b/Build/Build-Module.ps1 @@ -32,7 +32,7 @@ Build-Module -ModuleName 'Locksmith' { ProjectUri = 'https://github.com/jakehildreth/Locksmith' IconUri = 'https://raw.githubusercontent.com/jakehildreth/Locksmith/main/Images/locksmith.ico' PowerShellVersion = '5.1' - Tags = @('Windows', 'Locksmith', 'CA', 'PKI', 'ActiveDirectory', 'CertificateServices', 'ADCS') + Tags = @('Locksmith', 'ActiveDirectory', 'ADCS', 'CA', 'Certificate', 'CertificateAuthority', 'CertificateServices', 'PKI', 'X509', 'Windows') } New-ConfigurationManifest @Manifest From 0a932abf115ad28547c0cf68cf48efb577c803a2 Mon Sep 17 00:00:00 2001 From: Sam Erde Date: Tue, 4 Mar 2025 15:31:10 -0500 Subject: [PATCH 4/7] Add CmdletBinding attribute to merged script. --- Build/Build-Module.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Build/Build-Module.ps1 b/Build/Build-Module.ps1 index 1bf4d00..fed4774 100644 --- a/Build/Build-Module.ps1 +++ b/Build/Build-Module.ps1 @@ -120,10 +120,11 @@ Build-Module -ModuleName 'Locksmith' { New-ConfigurationBuild -Enable:$true -SignModule:$false -DeleteTargetModuleBeforeBuild -MergeModuleOnBuild #-UseWildcardForFunctions $PreScriptMerge = { + [CmdletBinding()] param ( [int]$Mode, [Parameter()] - [ValidateSet('Auditing','ESC1','ESC2','ESC3','ESC4','ESC5','ESC6','ESC8','ESC11','ESC13','ESC15','EKUwu','All','PromptMe')] + [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'ESC15', 'EKUwu', 'All', 'PromptMe')] [array]$Scans = 'All' ) } From f9d4f25d2bb5b1aa6912464a363376d8c1f93790 Mon Sep 17 00:00:00 2001 From: Sam Erde Date: Tue, 4 Mar 2025 15:36:04 -0500 Subject: [PATCH 5/7] Add full parameter definitions and validation to merged script. --- Build/Build-Module.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Build/Build-Module.ps1 b/Build/Build-Module.ps1 index fed4774..527ef38 100644 --- a/Build/Build-Module.ps1 +++ b/Build/Build-Module.ps1 @@ -120,9 +120,14 @@ Build-Module -ModuleName 'Locksmith' { New-ConfigurationBuild -Enable:$true -SignModule:$false -DeleteTargetModuleBeforeBuild -MergeModuleOnBuild #-UseWildcardForFunctions $PreScriptMerge = { - [CmdletBinding()] + [CmdletBinding(HelpUri = 'https://jakehildreth.github.io/Locksmith/Invoke-Locksmith')] param ( - [int]$Mode, + # The mode to run Locksmith in. Defaults to 0. + [Parameter(Mandatory = $false)] + [ValidateSet(0, 1, 2, 3, 4)] + [int]$Mode = 0, + + # The scans to run. Defaults to 'All'. [Parameter()] [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'ESC15', 'EKUwu', 'All', 'PromptMe')] [array]$Scans = 'All' From a49cd7b8096e31eb4f8a15c47e37b6954bed4c6a Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Fri, 28 Mar 2025 13:32:42 -0500 Subject: [PATCH 6/7] Fix #240 with updated URL --- Invoke-Locksmith.ps1 | 4 ++-- Locksmith.psd1 | 2 +- Private/Find-ESC15.ps1 | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index 783ec8c..1fb2bac 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -581,7 +581,7 @@ More info: Step 1: Open an elevated Powershell session as an AD or PKI Admin Step 2: Run Unpublish-SchemaV1Templates.ps1 #> -Invoke-WebRequest -Uri https://bit.ly/Fix-ESC15 | Invoke-Expression +Invoke-WebRequest -Uri https://gist.githubusercontent.com/jakehildreth/13c7d615adc905d317fc4379026ad28e/raw/Unpublish-SchemaV1Templates.ps1 | Invoke-Expression "@ Revert = '[TODO]' @@ -4390,7 +4390,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2025.2.22' + $Version = '2025.3.28' $LogoPart1 = @' _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| diff --git a/Locksmith.psd1 b/Locksmith.psd1 index b350ddc..66743e6 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -8,7 +8,7 @@ FunctionsToExport = 'Invoke-Locksmith' GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' HelpInfoURI = 'https://raw.githubusercontent.com/jakehildreth/Locksmith/main/en-US/' - ModuleVersion = '2025.2.22' + ModuleVersion = '2025.3.28' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ diff --git a/Private/Find-ESC15.ps1 b/Private/Find-ESC15.ps1 index 9654cac..ba99bb7 100644 --- a/Private/Find-ESC15.ps1 +++ b/Private/Find-ESC15.ps1 @@ -82,7 +82,7 @@ More info: Step 1: Open an elevated Powershell session as an AD or PKI Admin Step 2: Run Unpublish-SchemaV1Templates.ps1 #> -Invoke-WebRequest -Uri https://bit.ly/Fix-ESC15 | Invoke-Expression +Invoke-WebRequest -Uri https://gist.githubusercontent.com/jakehildreth/13c7d615adc905d317fc4379026ad28e/raw/Unpublish-SchemaV1Templates.ps1 | Invoke-Expression "@ Revert = '[TODO]' From 12333bede92d430818a3e540f23a5f97b5906198 Mon Sep 17 00:00:00 2001 From: Jake Hildreth Date: Sun, 20 Apr 2025 05:37:48 -0500 Subject: [PATCH 7/7] Fresh Build for 2025.04.20 release --- Invoke-Locksmith.ps1 | 53 ++++++++++++++++++++++++++------------------ Locksmith.psd1 | 4 ++-- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/Invoke-Locksmith.ps1 b/Invoke-Locksmith.ps1 index bc67b19..375745d 100644 --- a/Invoke-Locksmith.ps1 +++ b/Invoke-Locksmith.ps1 @@ -1,5 +1,11 @@ -param ( - [int]$Mode, +[CmdletBinding(HelpUri = 'https://jakehildreth.github.io/Locksmith/Invoke-Locksmith')] +param ( + # The mode to run Locksmith in. Defaults to 0. + [Parameter(Mandatory = $false)] + [ValidateSet(0, 1, 2, 3, 4)] + [int]$Mode = 0, + + # The scans to run. Defaults to 'All'. [Parameter()] [ValidateSet('Auditing', 'ESC1', 'ESC2', 'ESC3', 'ESC4', 'ESC5', 'ESC6', 'ESC8', 'ESC11', 'ESC13', 'ESC15', 'EKUwu', 'All', 'PromptMe')] [array]$Scans = 'All' @@ -2900,7 +2906,12 @@ function Set-AdditionalCAProperty { $CAHostDistinguishedName = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Server $ForestGC ).DistinguishedName $CAHostFQDN = (Get-ADObject -Filter { (Name -eq $CAHostName) -and (objectclass -eq 'computer') } -Properties DnsHostname -Server $ForestGC).DnsHostname } - $ping = if ($CAHostFQDN) { Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet } else { Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" } + $ping = if ($CAHostFQDN) { + Test-Connection -ComputerName $CAHostFQDN -Count 1 -Quiet + } + else { + Write-Warning "Unable to resolve $($_.Name) Fully Qualified Domain Name (FQDN)" + } if ($ping) { try { if ($Credential) { @@ -3395,23 +3406,23 @@ function Set-RiskRating { switch ($Issue.objectClass) { # Being able to modify Root CA Objects is very bad. 'certificationAuthority' { - $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' + $RiskValue += 2; $RiskScoring += 'Root Certification Authority bject: +2' } # Being able to modify Issuing CA Objects is also very bad. 'pKIEnrollmentService' { - $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' + $RiskValue += 2; $RiskScoring += 'Issuing Certification Authority Object: +2' } # Being able to modify CA Hosts? Yeah... very bad. 'computer' { - $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' + $RiskValue += 2; $RiskScoring += 'Certification Authority Host Computer: +2' } # Being able to modify OIDs could result in ESC13 vulns. 'msPKI-Enterprise-Oid' { - $RiskValue += 1; $RiskScoring += 'OID: +1' + $RiskValue += 1; $RiskScoring += 'OID: +1' } # Being able to modify PKS containers is bad. 'container' { - $RiskValue += 1; $RiskScoring += 'Container: +1' + $RiskValue += 1; $RiskScoring += 'Container: +1' } } } @@ -3420,19 +3431,19 @@ function Set-RiskRating { # Convert Value to Name $RiskName = switch ($RiskValue) { { $_ -le 1 } { - 'Informational' + 'Informational' } 2 { - 'Low' + 'Low' } 3 { - 'Medium' + 'Medium' } 4 { - 'High' + 'High' } { $_ -ge 5 } { - 'Critical' + 'Critical' } } @@ -3981,7 +3992,7 @@ Set-Acl -Path `$Path -AclObject `$ACL "@ } 4 { - break + break } 5 { $Issue.Fix = @" @@ -4144,10 +4155,10 @@ Function Write-HostColorized { # We precompile them for better performance with many input objects. [System.Text.RegularExpressions.RegexOptions] $reOpts = if ($CaseSensitive) { - 'Compiled, ExplicitCapture' + 'Compiled, ExplicitCapture' } else { - 'Compiled, ExplicitCapture, IgnoreCase' + 'Compiled, ExplicitCapture, IgnoreCase' } # Transform the dictionary: @@ -4169,10 +4180,10 @@ Function Write-HostColorized { } $colorArgs = @{ } if ($fg) { - $colorArgs['ForegroundColor'] = [ConsoleColor] $fg + $colorArgs['ForegroundColor'] = [ConsoleColor] $fg } if ($bg) { - $colorArgs['BackgroundColor'] = [ConsoleColor] $bg + $colorArgs['BackgroundColor'] = [ConsoleColor] $bg } # Consolidate the patterns into a single pattern with alternation ('|'), @@ -4191,7 +4202,7 @@ Function Write-HostColorized { } } catch { - throw + throw } # Construct the arguments to pass to Out-String. @@ -4214,7 +4225,7 @@ Function Write-HostColorized { foreach ($m in $entry.Key.Matches($_)) { @{ Index = $m.Index; Text = $m.Value; ColorArgs = $entry.Value } if ($WholeLine) { - break patternLoop + break patternLoop } } } @@ -4390,7 +4401,7 @@ function Invoke-Locksmith { [System.Management.Automation.PSCredential]$Credential ) - $Version = '2025.3.28' + $Version = '2025.4.20' $LogoPart1 = @' _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 66743e6..d6c714d 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -8,14 +8,14 @@ FunctionsToExport = 'Invoke-Locksmith' GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' HelpInfoURI = 'https://raw.githubusercontent.com/jakehildreth/Locksmith/main/en-US/' - ModuleVersion = '2025.3.28' + ModuleVersion = '2025.4.20' PowerShellVersion = '5.1' PrivateData = @{ PSData = @{ ExternalModuleDependencies = @('ActiveDirectory', 'ServerManager', 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.LocalAccounts', 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Security', 'CimCmdlets', 'Dism') IconUri = 'https://raw.githubusercontent.com/jakehildreth/Locksmith/main/Images/locksmith.ico' ProjectUri = 'https://github.com/jakehildreth/Locksmith' - Tags = @('Windows', 'Locksmith', 'CA', 'PKI', 'ActiveDirectory', 'CertificateServices', 'ADCS') + Tags = @('Locksmith', 'ActiveDirectory', 'ADCS', 'CA', 'Certificate', 'CertificateAuthority', 'CertificateServices', 'PKI', 'X509', 'Windows') } } RequiredModules = @('ActiveDirectory', 'ServerManager', 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.LocalAccounts', 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Security', 'CimCmdlets', 'Dism')