mirror of
https://github.com/subat0mik/Misconfiguration-Manager
synced 2026-06-08 17:38:34 +00:00
1106 lines
46 KiB
PowerShell
1106 lines
46 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Collect information from an SMS Provider and remote site systems to identify the issues described in Misconfiguration Manager
|
|
|
|
.DESCRIPTION
|
|
Author: Chris Thompson (@_Mayyhem)
|
|
Version: 1.0
|
|
|
|
Requirements:
|
|
- Run on an SMS Provider or target one using the -SMSProvider parameter
|
|
- Any SCCM Security Role (e.g., Read-only Analyst or higher)
|
|
- Use the -Verbose option to display the results of checks as they occur
|
|
|
|
Recommended to improve accuracy and reduce false positives:
|
|
- Local Administrators group privileges on site systems
|
|
- RPC and SMB connectivity to site systems
|
|
|
|
.PARAMETER SMSProvider
|
|
Specify a remote SMS Provider to run the script against.
|
|
|
|
.PARAMETER Timeout
|
|
Increase or decrease the connection timeout for remote site system checks (default: 5 seconds)
|
|
|
|
.PARAMETER Verbose
|
|
Enable verbose logging of script execution events and display check results as they occur.
|
|
|
|
.EXAMPLE
|
|
.\MisconfigurationManager.ps1 -Help
|
|
# Display help text
|
|
|
|
.EXAMPLE
|
|
.\MisconfigurationManager.ps1
|
|
# Collect information from a local SMS Provider and print only the final results after analysis.
|
|
|
|
.EXAMPLE
|
|
.\MisconfigurationManager.ps1 -SMSProvider <SMS_PROVIDER> -Timeout 2 -Verbose
|
|
# Collect information from a remote SMS Provider, give up on failed connections after 2 seconds, and print results as they occur.
|
|
|
|
.LINK
|
|
https://misconfigurationmanager.com
|
|
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$Help,
|
|
[string]$SMSProvider,
|
|
[int]$Timeout = 5
|
|
)
|
|
|
|
if ($Help) {
|
|
Get-Help $MyInvocation.MyCommand.Path
|
|
exit
|
|
}
|
|
|
|
if (-not $SMSProvider) {
|
|
$SMSProvider = $env:COMPUTERNAME
|
|
}
|
|
|
|
# Save settings
|
|
$originalVerbosePreference = $VerbosePreference
|
|
$originalWarningPreference = $WarningPreference
|
|
|
|
|
|
# Determine if the host is the console or ISE
|
|
if ($Host.Name -eq 'ConsoleHost') {
|
|
# For the standard console, we use ConsoleColor enum values
|
|
$originalVerboseColor = $Host.PrivateData.VerboseForegroundColor
|
|
$originalVerboseBackgroundColor = $Host.PrivateData.VerboseBackgroundColor
|
|
$originalWarningColor = $Host.PrivateData.WarningForegroundColor
|
|
$originalWarningBackgroundColor = $Host.PrivateData.WarningBackgroundColor
|
|
# Set the foreground colors and set the background colors to match the console's background
|
|
$Host.PrivateData.VerboseForegroundColor = 'Cyan'
|
|
$Host.PrivateData.WarningForegroundColor = 'DarkYellow'
|
|
$Host.PrivateData.VerboseBackgroundColor = $Host.UI.RawUI.BackgroundColor
|
|
$Host.PrivateData.WarningBackgroundColor = $Host.UI.RawUI.BackgroundColor
|
|
} elseif ($Host.Name -eq 'Windows PowerShell ISE Host') {
|
|
# For ISE, we use System.Windows.Media.Color values
|
|
$originalVerboseColor = $psISE.Options.VerboseForegroundColor
|
|
$originalWarningColor = $psISE.Options.WarningForegroundColor
|
|
$psISE.Options.VerboseForegroundColor = [System.Windows.Media.Colors]::Cyan
|
|
$psISE.Options.WarningForegroundColor = [System.Windows.Media.Colors]::DarkOrange
|
|
}
|
|
|
|
|
|
|
|
# Set output preferences
|
|
if ($VerbosePreference) {
|
|
$VerbosePreference = 'Continue'
|
|
$WarningPreference = 'Continue'
|
|
}
|
|
else {
|
|
$VerbosePreference = 'SilentlyContinue'
|
|
$WarningPreference = 'SilentlyContinue'
|
|
}
|
|
|
|
# Display help text
|
|
if ($Help) {
|
|
Get-Help $MyInvocation.MyCommand.Path
|
|
exit
|
|
}
|
|
|
|
function Check-AccountIsLocalAdmin {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$AccountName,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ComputerName
|
|
)
|
|
|
|
Write-Verbose " Checking local Administrators group on $ComputerName for account: $AccountName"
|
|
|
|
try {
|
|
$scriptBlock = {
|
|
param($AccountName, $ComputerName)
|
|
Get-WmiObject Win32_GroupUser -ComputerName $ComputerName |
|
|
Where-Object { $_.GroupComponent -like '*"Administrators"' } |
|
|
Where-Object { $_.PartComponent -like "*`"$using:AccountName`"*" } |
|
|
ForEach-Object { $_.PartComponent } |
|
|
ForEach-Object { $_.Split('=')[2].Trim('"') }
|
|
}
|
|
|
|
$matchingAdminAccounts = Run-Script -ScriptBlock $scriptBlock -ArgumentList $AccountName, $ComputerName -TimeoutSeconds $Timeout
|
|
|
|
# Check succeeded and found a matching account in local Administrators group
|
|
if ($matchingAdminAccounts) {
|
|
return $true
|
|
}
|
|
|
|
# Check timed out
|
|
elseif ($matchingAdminAccounts -like "*timed out*") {
|
|
return "Check for local Administrators group members timed out after $Timeout seconds"
|
|
}
|
|
|
|
# Check succeeded but no matches
|
|
else {
|
|
return $false
|
|
}
|
|
}
|
|
catch {
|
|
return "Failed to check local Administrators group members: $($_.ToString())"
|
|
}
|
|
}
|
|
|
|
function Check-IssueStatus {
|
|
param (
|
|
[string]$Issue,
|
|
[bool]$LikelyCondition,
|
|
[bool]$PreventingCondition,
|
|
[string]$LikelyMessage,
|
|
[string]$FailedCheckMessage,
|
|
[string]$PreventingMessage,
|
|
[string]$RolePrefix,
|
|
[ref]$System
|
|
)
|
|
|
|
if ($System.Value.IssuesToCheck -contains $Issue) {
|
|
|
|
$message =
|
|
if ($PreventingCondition) {
|
|
$PreventingMessage
|
|
$System.Value.IssuesToCheck = $System.Value.IssuesToCheck | Where-Object { $_ -ne $Issue }
|
|
}
|
|
elseif ($LikelyCondition) {
|
|
$LikelyMessage
|
|
}
|
|
else {
|
|
$FailedCheckMessage
|
|
}
|
|
$System.Value.Output += "$RolePrefix $message`n"
|
|
}
|
|
}
|
|
|
|
# Function converted to string to load in scriptblock to reduce connection timeouts
|
|
$getRegistrySubkeyValueFunction = @'
|
|
function Get-RegistrySubkeyValue {
|
|
param (
|
|
[string]$ComputerName,
|
|
[string]$Hive,
|
|
[string]$SubKeyPath,
|
|
[string]$ValueName
|
|
)
|
|
|
|
# Define the registry hive, subkey, and value name you want to read
|
|
$registryHive = if ($Hive) { $Hive } else { [Microsoft.Win32.RegistryHive]::LocalMachine }
|
|
|
|
try {
|
|
# Open the remote registry key
|
|
$remoteRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($registryHive, $ComputerName)
|
|
$subKey = $remoteRegistry.OpenSubKey($SubKeyPath)
|
|
|
|
# Read the value
|
|
if ($subKey -ne $null) {
|
|
$value = $subKey.GetValue($ValueName)
|
|
return $value
|
|
} else {
|
|
return "Subkey $SubKeyPath not found on $ComputerName"
|
|
}
|
|
} catch {
|
|
return "Failed to read registry on ${ComputerName}: $_"
|
|
} finally {
|
|
if ($subKey -ne $null) {
|
|
$subKey.Close()
|
|
}
|
|
if ($remoteRegistry -ne $null) {
|
|
$remoteRegistry.Close()
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
|
|
|
|
function Get-SiteDatabaseEPA {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ComputerName
|
|
)
|
|
|
|
# Get the list of all namespaces under 'root\Microsoft\SqlServer'
|
|
$serverNamespaceRoot = "root\Microsoft\SqlServer"
|
|
|
|
$scriptBlock = {
|
|
param($computerName, $namespaceRoot)
|
|
Get-WmiObject -ComputerName $computerName -Namespace $namespaceRoot -Class "__NAMESPACE"
|
|
}
|
|
|
|
$namespaces = Run-Script -ScriptBlock $scriptBlock -ArgumentList $ComputerName, $serverNamespaceRoot -TimeoutSeconds $Timeout -ErrorAction SilentlyContinue
|
|
|
|
# Filter out the namespaces that match the pattern 'ComputerManagementXX'
|
|
$cmNamespaces = $namespaces | Where-Object { $_.Name -match "^ComputerManagement\d+$" }
|
|
|
|
if ($cmNamespaces) {
|
|
# Iterate over each namespace and perform the query
|
|
foreach ($namespace in $cmNamespaces) {
|
|
|
|
$fullNamespace = "$serverNamespaceRoot\$($namespace.Name)"
|
|
|
|
# Query the WMI object for Extended Protection setting
|
|
$wmiQuery = "SELECT * FROM ServerSettingsExtendedProtection"
|
|
|
|
# Execute the WMI query
|
|
try {
|
|
$extendedProtectionSettings = Get-WmiObject -ComputerName $ComputerName -Namespace $fullNamespace -Query $wmiQuery
|
|
return $extendedProtectionSettings.ExtendedProtection
|
|
}
|
|
catch {
|
|
return "Failed to check EPA requirements: $($_.ToString())"
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return "Check for EPA requirements timed out after $Timeout seconds"
|
|
}
|
|
}
|
|
|
|
function Get-SiteHierarchy {
|
|
param (
|
|
[string]$Namespace,
|
|
[string]$ParentSiteCode = $null,
|
|
[string]$SMSProvider
|
|
)
|
|
|
|
# Initialize output variable, a list of sites in the hierarchy
|
|
$siteHierarchy = @()
|
|
|
|
# Query the top level site first
|
|
$filter = if ($ParentSiteCode) { "ParentSiteCode = '$ParentSiteCode'" } else { "ParentSiteCode = ''" }
|
|
Write-Verbose "Querying $($Namespace).SMS_SCI_SiteDefinition for the list of sites with parent: $ParentSiteCode"
|
|
|
|
$scriptBlock = {
|
|
param($Namespace, $Filter, $SMSProvider)
|
|
return Get-WmiObject -Namespace $Namespace -Class SMS_SCI_SiteDefinition -Filter $Filter -ComputerName $SMSProvider
|
|
}
|
|
|
|
$sites = Run-Script -ScriptBlock $scriptBlock -ArgumentList $Namespace, $filter, $SMSProvider -TimeoutSeconds $Timeout
|
|
|
|
# Exit loop if job times out or isn't completed
|
|
if ($sites) {
|
|
if ($sites -like "*timed out*") {
|
|
Write-Warning "Query timed out: $ParentSiteCode doesn't have any child sites or the site database is offline"
|
|
return
|
|
}
|
|
elseif ($sites[0].ToString() -like "*PSRemotingJob") {
|
|
Write-Warning "The timeout is too short for jobs to finish"
|
|
return
|
|
}
|
|
}
|
|
|
|
foreach ($site in $sites) {
|
|
|
|
Write-Verbose ("Gathering data for site: {0} ({1})" -f $site.SiteName, $site.SiteCode)
|
|
|
|
$currentSite = @{
|
|
"AutomaticClientPush" = $null
|
|
"ClearClientInstalledFlag" = $null
|
|
"ClientPushAccounts" = @()
|
|
"ClientPushTargets" = $null
|
|
"FallbackToNTLM" = $null
|
|
"ParentSiteCode" = $site.ParentSiteCode
|
|
"SiteCode" = $site.SiteCode
|
|
"SiteName" = $site.SiteName
|
|
"SiteServerName" = $site.SiteServerName
|
|
"SiteSystems" = @()
|
|
"Type" = $site.SiteType
|
|
"TypeDepth" = $null
|
|
"TypeName" = $null
|
|
}
|
|
|
|
# Get the hierarchy level
|
|
$currentSite = Get-SiteType -Site $([ref]$currentSite)
|
|
Write-Verbose ("{0} is a {1}" -f $currentSite.SiteName, $currentSite.TypeName)
|
|
|
|
# Indent based on the hierarchy level
|
|
$indent = " " * ($currentSite.TypeDepth * 4)
|
|
|
|
# Query other site system roles
|
|
Get-SiteSystems -Namespace $Namespace -Site $([ref]$currentSite) -Indent ($Indent + " ") -SMSProvider $SMSProvider
|
|
|
|
# Get client push installation settings for primary sites
|
|
if ($currentSite.Type -eq 2) {
|
|
Get-SitePushSettings -Namespace $namespace -Site $currentSite -ComputerName $SMSProvider
|
|
}
|
|
|
|
# Add the site to the list of sites
|
|
$siteHierarchy += $currentSite
|
|
|
|
# Recursive call for child sites excluding secondary sites
|
|
Get-SiteHierarchy -Namespace $Namespace -ParentSiteCode $site.SiteCode -SMSProvider $SMSProvider
|
|
}
|
|
return $siteHierarchy
|
|
}
|
|
|
|
function Get-SiteNamespace {
|
|
param (
|
|
[string]$SMSProvider
|
|
)
|
|
|
|
# Query WMI to get all SMS namespaces
|
|
Write-Verbose "Looking for site namespace in root\SMS on $SMSProvider"
|
|
try {
|
|
$namespaces = Get-WmiObject -Namespace "root\SMS" -Class "__NAMESPACE" -ComputerName $SMSProvider -ErrorAction Stop
|
|
}
|
|
|
|
catch {
|
|
Write-Warning "Could not find root\SMS namespace. Is $SMSProvider an SMS Provider?"
|
|
exit
|
|
}
|
|
|
|
$foundNamespace = $null
|
|
|
|
foreach ($ns in $namespaces) {
|
|
# Check if the namespace is like SMS_<SiteCode>
|
|
if ($ns.Name -match '^site_') {
|
|
$foundNamespace = "root\SMS\" + $ns.Name
|
|
break
|
|
}
|
|
}
|
|
Write-Verbose "Found $foundNamespace on $SMSProvider"
|
|
return $foundNamespace
|
|
}
|
|
|
|
function Get-SitePushSettings {
|
|
param (
|
|
[string]$ComputerName,
|
|
[string]$Namespace,
|
|
$Site
|
|
)
|
|
|
|
Write-Verbose "Querying client push installation settings for $($Site.SiteCode)"
|
|
|
|
$queryAutomaticClientPush = "SELECT PropertyName, Value, Value1 FROM SMS_SCI_SCProperty WHERE SiteCode='$($Site.SiteCode)' AND ItemType='SMS_DISCOVERY_DATA_MANAGER' AND PropertyName='SETTINGS'"
|
|
try {
|
|
$result = Get-WmiObject -Namespace $Namespace -Query $queryAutomaticClientPush -ComputerName $ComputerName
|
|
if ($result) {
|
|
if ($result.Value1 -eq "Active") {
|
|
Write-Warning " Automatic site-wide client push installation is enabled"
|
|
$Site.AutomaticClientPush = $true
|
|
}
|
|
elseif ($result.Value1 -eq "INACTIVE") {
|
|
Write-Verbose " Automatic site-wide client push installation is not enabled"
|
|
$Site.AutomaticClientPush = $false
|
|
}
|
|
else {
|
|
Write-Warning " Check for automatic site-wide client push installation settings failed"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " An error occurred while querying client push settings for site $($siteCode.SiteCode): $($_.Exception.Message)" -ErrorAction 'Continue'
|
|
}
|
|
|
|
|
|
$queryFallbackToNTLM = "SELECT PropertyName, Value, Value1 FROM SMS_SCI_SCProperty WHERE SiteCode='$($Site.SiteCode)' AND ItemType='SMS_DISCOVERY_DATA_MANAGER' AND PropertyName='ENABLEKERBEROSCHECK'"
|
|
try {
|
|
$result = Get-WmiObject -Namespace $Namespace -Query $queryFallbackToNTLM -ComputerName $ComputerName
|
|
if ($result) {
|
|
if ($result.Value -eq 3) {
|
|
Write-Warning " Fallback to NTLM is enabled"
|
|
$Site.FallbackToNTLM = $true
|
|
}
|
|
elseif ($result.Value -eq 2) {
|
|
Write-Verbose " Fallback to NTLM is not enabled"
|
|
$Site.FallbackToNTLM = $false
|
|
}
|
|
else {
|
|
Write-Warning " Check for fallback to NTLM setting failed"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " An error occurred while querying client push settings for site $($siteCode.SiteCode): $($_.Exception.Message)" -ErrorAction 'Continue'
|
|
}
|
|
|
|
if ($Site.AutomaticClientPush -and $Site.FallbackToNTLM) {
|
|
$queryClientPushTargets = "SELECT PropertyName, Value, Value1 FROM SMS_SCI_SCProperty WHERE SiteCode='$($Site.SiteCode)' AND ItemType='SMS_DISCOVERY_DATA_MANAGER' AND PropertyName='FILTERS'"
|
|
try {
|
|
$result = Get-WmiObject -Namespace $Namespace -Query $queryClientPushTargets -ComputerName $ComputerName
|
|
if ($result) {
|
|
Write-Warning " Install client software on the following computers:"
|
|
$Site.ClientPushTargets =
|
|
switch ($result.Value) {
|
|
0 { " Workstations and Servers (including domain controllers)" }
|
|
1 { " Servers only (including domain controllers)" }
|
|
2 { " Workstations and Servers (excluding domain controllers)" }
|
|
3 { " Servers only (excluding domain controllers)" }
|
|
4 { " Workstations and domain controllers only (excluding other servers)" }
|
|
5 { " Domain controllers only" }
|
|
6 { " Workstations only" }
|
|
7 { " No computers" }
|
|
}
|
|
Write-Warning $Site.ClientPushTargets
|
|
}
|
|
else {
|
|
Write-Warning " Check for client push targets failed"
|
|
}
|
|
|
|
$queryAccounts = "SELECT Values FROM SMS_SCI_SCPropertyList WHERE PropertyListName='Reserved2'"
|
|
$accounts = Get-WmiObject -Namespace $Namespace -Query $queryAccounts -ComputerName $ComputerName
|
|
if ($accounts.Values) {
|
|
foreach ($value in $accounts.Values) {
|
|
Write-Warning " Discovered client push installation account: $value"
|
|
$Site.ClientPushAccounts += $value
|
|
}
|
|
}
|
|
else {
|
|
Write-Warning " No client push installation accounts were configured, but the server may still use its machine account"
|
|
|
|
}
|
|
|
|
# Always add the site server computer account to client installation accounts
|
|
$Site.ClientPushAccounts += "$($Site.SiteServerName.Split('.')[0])$"
|
|
|
|
$queryTask = "SELECT * FROM SMS_SCI_SQLTask WHERE ItemName='Clear Undiscovered Clients'"
|
|
$task = Get-WmiObject -Namespace $Namespace -Query $queryTask -ComputerName $ComputerName
|
|
if ($task.Enabled -eq $true) {
|
|
Write-Warning " The client installed flag is automatically cleared on inactive clients after $($task.DeleteOlderThan) days, resulting in automatic client push for reinstallation"
|
|
$Site.ClearClientInstalledFlag = $true
|
|
}
|
|
elseif ($task.Enabled -eq $false) {
|
|
Write-Verbose " The client installed flag is not automatically cleared on inactive clients, preventing automatic reinstallation"
|
|
$Site.ClearClientInstalledFlag = $false
|
|
}
|
|
else {
|
|
Write-Verbose " Check for clear client installed flag failed"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning " An error occurred while querying client push settings for site $($siteCode.SiteCode): $($_.Exception.Message)" -ErrorAction 'Continue'
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-SiteSystems {
|
|
param (
|
|
[string]$Namespace,
|
|
[ref]$Site,
|
|
[string]$SMSProvider
|
|
)
|
|
|
|
# Query SMS_SCI_SysResUse class for site system roles
|
|
Write-Verbose "Querying the list of systems in $($Site.Value.SiteCode)"
|
|
$siteSystemRoles = Get-WmiObject -Namespace $Namespace -Class SMS_SCI_SysResUse -Filter "SiteCode = '$($Site.Value.SiteCode)'" -ComputerName $SMSProvider
|
|
|
|
# Group roles by NetworkOSPath
|
|
$siteSystems = $siteSystemRoles | Group-Object -Property NetworkOSPath
|
|
|
|
foreach ($siteSystem in $siteSystems) {
|
|
|
|
$siteSystemName = $siteSystem.Name.TrimStart('\')
|
|
Write-Verbose "Collecting data for $siteSystemName"
|
|
$isRemote = $siteSystemName -ne $Site.Value.SiteServerName
|
|
|
|
$currentSiteSystem = @{
|
|
# Check whether the role is on a remote server (not the site server), making NTLM relay possible
|
|
"Name" = $siteSystemName
|
|
"EPARequired" = $null
|
|
"IsRemote" = $isRemote
|
|
"IssuesToCheck" = @()
|
|
"Output" = $null
|
|
"SiteCode" = $Site.Value.SiteCode
|
|
"SiteSystemRoles" = @()
|
|
"SMBSigningRequired" = $null
|
|
"WebClientStatus" = $null
|
|
}
|
|
|
|
foreach ($role in $siteSystem.Group) {
|
|
$currentSiteSystem.SiteSystemRoles += $role.RoleName
|
|
Write-Verbose " $($role.RoleName)"
|
|
}
|
|
|
|
# Check SMB signing requirements for TAKEOVER-2, TAKEOVER-4, TAKEOVER-6, TAKEOVER-7, and ELEVATE-1
|
|
Write-Verbose " Collecting SMB signing requirements"
|
|
$currentSiteSystem.SMBSigningRequired = Get-SMBSigningRequirement -ComputerName $currentSiteSystem.Name
|
|
|
|
if ($currentSiteSystem.IsRemote) {
|
|
|
|
foreach ($role in $siteSystem.Group) {
|
|
|
|
if ($role.RoleName -eq "SMS SQL Server" -and $Site.Type -ne 1) {
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-1", "TAKEOVER-2"
|
|
|
|
# TAKEOVER-2
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "TAKEOVER-2"
|
|
|
|
# TAKEOVER-1
|
|
Write-Verbose " Collecting EPA requirements"
|
|
$currentSiteSystem.EPARequired = Get-SiteDatabaseEPA -ComputerName $currentSiteSystem.Name
|
|
|
|
if ($currentSiteSystem.EPARequired -eq 2) {
|
|
Write-Verbose " EPA required: True"
|
|
}
|
|
elseif ($currentSiteSystem.EPARequired -lt 2) {
|
|
Write-Warning " EPA required: False (TAKEOVER-1 likely!)"
|
|
}
|
|
else {
|
|
Write-Warning " $($currentSiteSystem.EPARequired) (check TAKEOVER-1 manually)"
|
|
}
|
|
}
|
|
|
|
elseif ($role.RoleName -eq "SMS Provider") {
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-5", "TAKEOVER-6"
|
|
|
|
# TAKEOVER-5 cannot be prevented on the relay target because AdminService does not support EPA
|
|
|
|
# TAKEOVER-6
|
|
if ($currentSiteSystem.SMBSigningRequired -eq 1) {
|
|
$currentSiteSystem.IssuesToCheck = $currentSiteSystem.IssuesToCheck | Where-Object { $_ -ne "TAKEOVER-6" }
|
|
}
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "TAKEOVER-6"
|
|
}
|
|
|
|
# Add ELEVATE-1 if no TAKEOVER techniques are present
|
|
elseif ($currentSiteSystem.IssuesToCheck -notcontains "ELEVATE-1" -and ($currentSiteSystem.IssuesToCheck -match '^TAKEOVER.*').Count -eq 0) {
|
|
$currentSiteSystem.IssuesToCheck += "ELEVATE-1"
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "ELEVATE-1"
|
|
}
|
|
}
|
|
|
|
# This is a site server
|
|
}
|
|
else {
|
|
|
|
# Don't add TAKEOVERs to secondary site servers
|
|
if ($Site.Value.Type -ne 1) {
|
|
|
|
# TAKEOVER-3 is applicable if AD CS is in use (check manually)
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-3"
|
|
|
|
# TAKEOVER-7 is applicable to sites with passive site servers
|
|
if ($currentSiteSystem.Name -ne $Site.Value.SiteServerName) {
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-7"
|
|
Write-Verbose " This system is a passive site server"
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "TAKEOVER-7"
|
|
}
|
|
|
|
# Print the SMB signing status even if no attack techniques are detected
|
|
else {
|
|
if ($currentSiteSystem.SMBSigningRequired -eq 1) {
|
|
Write-Verbose " SMB signing required: True"
|
|
}
|
|
elseif ($currentSiteSystem.SMBSigningRequired -eq 0) {
|
|
Write-Warning " SMB signing required: False"
|
|
}
|
|
else {
|
|
Write-Warning " SMB signing required: $($CurrentSiteSystem.SMBSigningRequired)"
|
|
}
|
|
}
|
|
|
|
# TAKEOVER-8 is applicable if WebClient is running on the site server
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-8"
|
|
Write-Verbose " Collecting WebClient service status"
|
|
$currentSiteSystem.WebClientStatus = Get-WebClientService -ComputerName $siteSystemName
|
|
|
|
if ($currentSiteSystem.WebClientStatus -eq "Not installed") {
|
|
Write-Verbose " WebClient: Not installed, preventing TAKEOVER-8"
|
|
}
|
|
elseif ($currentSiteSystem.WebClientStatus -eq "Running") {
|
|
Write-Warning " WebClient: Running (TAKEOVER-8 likely!)"
|
|
}
|
|
elseif ($currentSiteSystem.WebClientStatus -eq "Installed") {
|
|
Write-Warning " WebClient: Installed (TAKEOVER-8 possible if it ever starts!)"
|
|
}
|
|
else {
|
|
Write-Warning " WebClient check failed, validate TAKEOVER-8 manually"
|
|
}
|
|
}
|
|
|
|
# Add ELEVATE-1 to secondary site servers
|
|
else {
|
|
$currentSiteSystem.IssuesToCheck += "ELEVATE-1"
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "ELEVATE-1"
|
|
}
|
|
|
|
# Get site server computer account name from CAS, which should be processed first
|
|
if ($Site.Value.Type -eq 4) {
|
|
$Global:casComputerAccount = "$($Site.Value.SiteServerName.Split('.')[0])$"
|
|
}
|
|
|
|
# TAKEOVER-4 Check whether the CAS computer account is a local admin on primary site servers
|
|
elseif ($Site.Value.Type -eq 2 -and $Global:casComputerAccount) {
|
|
$currentSiteSystem.IssuesToCheck += "TAKEOVER-4"
|
|
$isLocalAdmin = Check-AccountIsLocalAdmin -AccountName $casComputerAccount -ComputerName $currentSiteSystem.Name
|
|
if ($casComputerAccount -eq $isLocalAdmin) {
|
|
Write-Warning " $casComputerAccount is a local admin on $currentSiteSystem.Name (TAKEOVER-4 possible)"
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "TAKEOVER-4"
|
|
}
|
|
elseif ($isLocalAdmin -contains "Failed") {
|
|
Write-Warning " Failed to check whether $casComputerAccount is a local admin on $($currentSiteSystem.Name) (check TAKEOVER-4 manually)"
|
|
Print-SMBSigningStatus -CurrentSiteSystem $currentSiteSystem -Issue "TAKEOVER-4"
|
|
}
|
|
else {
|
|
Write-Verbose " $casComputerAccount is not a local admin on $($currentSiteSystem.Name)"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Add the current site system to the site
|
|
$Site.Value.SiteSystems += $currentSiteSystem
|
|
}
|
|
}
|
|
|
|
function Get-SiteType {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[ref]$Site
|
|
)
|
|
|
|
if ($Site.Value.Type -eq 1) {
|
|
$Site.Value.TypeName = "secondary site"
|
|
$Site.Value.TypeDepth = 3
|
|
}
|
|
elseif ($Site.Value.Type -eq 2) {
|
|
$Site.Value.TypeName = "primary site"
|
|
$Site.Value.TypeDepth = 2
|
|
}
|
|
elseif ($Site.Value.Type -eq 4) {
|
|
$Site.Value.TypeName = "central administration site"
|
|
$Site.Value.TypeDepth = 1
|
|
}
|
|
return $Site.Value
|
|
}
|
|
|
|
function Get-SMBSigningRequirement {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ComputerName
|
|
)
|
|
|
|
$subKeyPath = "System\CurrentControlSet\Services\LanManServer\Parameters\"
|
|
$valueName = "RequireSecuritySignature"
|
|
|
|
$scriptBlock = {
|
|
param($functionString, $computerName, $subKeyPath, $valueName)
|
|
|
|
# Create the function in this scope
|
|
Invoke-Expression $functionString
|
|
|
|
$requireSecuritySignature = Get-RegistrySubkeyValue -ComputerName $computerName -SubKeyPath $subKeyPath -ValueName $valueName
|
|
return $requireSecuritySignature
|
|
}
|
|
|
|
# Run on timer to reduce wait time when the network path can't be found
|
|
try {
|
|
$requireSecuritySignature = Run-Script -ScriptBlock $scriptBlock -ArgumentList $getRegistrySubkeyValueFunction, $ComputerName, $subKeyPath, $valueName -TimeoutSeconds $Timeout
|
|
if ($requireSecuritySignature -like "*timed out*") {
|
|
return "Check for SMB signing requirements timed out after $Timeout seconds"
|
|
}
|
|
return $requireSecuritySignature
|
|
}
|
|
catch {
|
|
return "Failed to check SMB signing requirements: $($_.ToString())"
|
|
}
|
|
}
|
|
|
|
function Get-WebClientService {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ComputerName
|
|
)
|
|
|
|
try {
|
|
# Check if the remote computer is accessible within 3 seconds
|
|
$ping = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet
|
|
if ($ping) {
|
|
$service = Get-Service -Name WebClient -ComputerName $ComputerName -ErrorAction Stop
|
|
return $service.Status
|
|
}
|
|
else {
|
|
return "Failed to connect to $ComputerName to check for WebClient"
|
|
}
|
|
}
|
|
catch {
|
|
return "Failed to connect or an unexpected error occurred while checking for WebClient"
|
|
}
|
|
}
|
|
|
|
function Print-SiteStructure {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
$Site,
|
|
[string]$Indent = "",
|
|
[array]$AllSites
|
|
)
|
|
|
|
try {
|
|
# Determine if there are any child sites for this site
|
|
$childSites = $AllSites | Where-Object { $_.ParentSiteCode -eq $Site.SiteCode }
|
|
$hasChildSites = $childSites.Count -gt 0
|
|
|
|
# Display the current site details
|
|
$siteDetails = "Site Code: $($Site.SiteCode), Name: $($Site.SiteName), Site Server: $($Site.SiteServerName)"
|
|
if ($Site.ParentSiteCode) {
|
|
$siteDetails += ", Reporting to: $($Site.ParentSiteCode)"
|
|
}
|
|
|
|
# Link site to parent in tree and add space between items
|
|
$siteIndent = if ($Site.ParentSiteCode -and $hasChildSites) { $Indent.Substring(0, $Indent.Length - 4) + "├───" }
|
|
elseif ($Site.ParentSiteCode) { $Indent.Substring(0, $Indent.Length - 4) + "└───" }
|
|
else { $Indent }
|
|
|
|
$afterDetailsSpace =
|
|
# This site has child sites
|
|
if ($hasChildSites) {
|
|
" │ " * $Site.TypeDepth
|
|
}
|
|
# This is a standalone primary site
|
|
elseif ($Indent.Length -eq 0) {
|
|
" │ "
|
|
}
|
|
# This is a descendent site
|
|
else {
|
|
$Indent.Substring(0, $Indent.Length - 4) + " │"
|
|
}
|
|
|
|
Write-Host "$siteIndent$siteDetails`n$afterDetailsSpace"
|
|
|
|
# Print client push settings for primary sites
|
|
if ($Site.Type -eq 2) {
|
|
$message = $null
|
|
if ($Site.AutomaticClientPush -eq $true) {
|
|
$message = "$Indent ├───Automatic site-wide client push installation is enabled`n"
|
|
|
|
# Print relevant settings if automatic push is enabled
|
|
if ($Site.FallbackToNTLM -eq 3) {
|
|
$message += "$Indent │ Fallback to NTLM is enabled (ELEVATE-2 and ELEVATE-3 likely!)`n"
|
|
}
|
|
elseif ($Site.FallbackToNTLM -eq 2) {
|
|
$message += "$Indent │ Fallback to NTLM is not enabled`n"
|
|
}
|
|
else {
|
|
$message += "$Indent │ Check for fallback to NTLM setting failed`n"
|
|
}
|
|
|
|
$message += "$Indent │ Install client software on the following computers:`n"
|
|
$message += "$Indent │ $($Site.ClientPushTargets)`n"
|
|
$message += "$Indent │ Discovered client push installation accounts:`n"
|
|
if ($Site.ClientPushAccounts.Count -gt 0) {
|
|
foreach ($value in $Site.ClientPushAccounts) {
|
|
$message += "$Indent │ $value`n"
|
|
}
|
|
}
|
|
else {
|
|
$message += "$Indent │ No client push installation accounts were configured, but the server may still use its machine account`n"
|
|
}
|
|
|
|
if ($Site.ClearInstalledFlag -eq $true) {
|
|
$message += "$Indent │ The client installed flag is automatically cleared on inactive clients after $($task.DeleteOlderThan) days, resulting in automatic client push for reinstallation"
|
|
}
|
|
else {
|
|
$message += "$Indent │ The client installed flag is not automatically cleared on inactive clients, preventing automatic reinstallation"
|
|
}
|
|
}
|
|
elseif ($Site.AutomaticClientPush -eq $false) {
|
|
$message += "$Indent ├───Automatic site-wide client push installation is not enabled"
|
|
}
|
|
else {
|
|
$message += "$Indent ├───Check for automatic site-wide client push installation settings failed"
|
|
}
|
|
Write-Host $message
|
|
Write-Host "$Indent │"
|
|
}
|
|
|
|
# Get primary site server object
|
|
$primarySiteServer = $Site.SiteSystems | Where-Object { $_.Name.TrimStart('\') -eq $Site.SiteServerName }
|
|
|
|
# Display site systems
|
|
$siteSystemCount = $Site.SiteSystems.Count
|
|
|
|
foreach ($system in $Site.SiteSystems) {
|
|
|
|
# Remove ELEVATE-1 if any TAKEOVER is present
|
|
if ($system.IssuesToCheck -contains "ELEVATE-1" -and ($currentSiteSystem.IssuesToCheck -match '^TAKEOVER.*').Count -gt 0) {
|
|
$system.IssuesToCheck = $system.IssuesToCheck | Where-Object { $_ -ne "ELEVATE-1" }
|
|
}
|
|
|
|
# Do not continue tree structure if on the last system in the site
|
|
$siteSystemCount--
|
|
$isLastSystem = $siteSystemCount -eq 0
|
|
|
|
if ($isLastSystem -and -not $hasChildSites) {
|
|
# This is a standalone primary site
|
|
if ($Indent.Length -eq 0) {
|
|
$systemPrefix = " └───"
|
|
}
|
|
# This is a descendent site
|
|
else {
|
|
$systemPrefix = $Indent.Substring(0, $Indent.Length - 4) + " └───"
|
|
}
|
|
}
|
|
# This site has child sites
|
|
else {
|
|
$systemPrefix = "$Indent ├───"
|
|
}
|
|
|
|
$remoteText = if ($system.IsRemote) { " (Remote: True)" } else { "" }
|
|
$system.Output = "$systemPrefix$($system.Name)$remoteText`n"
|
|
|
|
# Do not continue tree structure if on the last role in the site
|
|
$roleCount = $system.SiteSystemRoles.Count
|
|
foreach ($role in $system.SiteSystemRoles) {
|
|
$roleCount--
|
|
|
|
if ($isLastSystem -and -not $hasChildSites) {
|
|
# This is a standalone primary site
|
|
if ($Indent.Length -eq 0) {
|
|
$rolePrefix = " "
|
|
}
|
|
# This is a descendent site
|
|
else {
|
|
$rolePrefix = $Indent.Substring(0, $Indent.Length - 4) + " "
|
|
}
|
|
}
|
|
else {
|
|
$rolePrefix = "$Indent │ "
|
|
}
|
|
|
|
$system.Output += "$rolePrefix $role`n"
|
|
|
|
# Issue details
|
|
if ($role -eq "SMS SQL Server") {
|
|
|
|
# TAKEOVER-1
|
|
Check-IssueStatus -Issue "TAKEOVER-1" `
|
|
-FailedCheckMessage "EPA check failed, validate TAKEOVER-1 manually" `
|
|
-LikelyCondition ($system.EPARequired -lt 2) `
|
|
-LikelyMessage "EPA not required, TAKEOVER-1 likely!" `
|
|
-PreventingCondition ($system.EPARequired -eq 2) `
|
|
-PreventingMessage "EPA required, preventing TAKEOVER-1" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
|
|
# TAKEOVER-2
|
|
Check-IssueStatus -Issue "TAKEOVER-2" `
|
|
-FailedCheckMessage "SMB signing check failed, validate TAKEOVER-2 manually" `
|
|
-LikelyCondition ($system.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, TAKEOVER-2 likely!" `
|
|
-PreventingCondition ($system.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing TAKEOVER-2" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
|
|
# TAKEOVER-9 - Not yet implemented
|
|
}
|
|
|
|
elseif ($role -eq "SMS Site Server") {
|
|
|
|
# TAKEOVER-3
|
|
|
|
|
|
# TAKEOVER-4
|
|
Check-IssueStatus -Issue "TAKEOVER-4" `
|
|
-FailedCheckMessage "SMB signing check failed, validate TAKEOVER-4 manually" `
|
|
-LikelyCondition ($system.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, TAKEOVER-4 likely!" `
|
|
-PreventingCondition ($system.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing TAKEOVER-4" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
|
|
# TAKEOVER-7 check on both active and passive site servers
|
|
if ($system.IssuesToCheck -contains "TAKEOVER-7" -and $primarySiteServer.IssuesToCheck -notcontains "TAKEOVER-7") {
|
|
$primarySiteServer.IssuesToCheck += "TAKEOVER-7"
|
|
}
|
|
|
|
# Active site server
|
|
Check-IssueStatus -Issue "TAKEOVER-7" `
|
|
-FailedCheckMessage "SMB signing check failed, validate TAKEOVER-7 manually" `
|
|
-LikelyCondition ($primarySiteServer.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, TAKEOVER-7 likely!" `
|
|
-PreventingCondition ($primarySiteServer.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing TAKEOVER-7" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$primarySiteServer)
|
|
|
|
# Passive site servers
|
|
Check-IssueStatus -Issue "TAKEOVER-7" `
|
|
-FailedCheckMessage "SMB signing check failed, validate TAKEOVER-7 manually" `
|
|
-LikelyCondition ($system.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, TAKEOVER-7 likely!" `
|
|
-PreventingCondition ($system.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing TAKEOVER-7" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
|
|
# TAKEOVER-8
|
|
if ($system.IssuesToCheck -contains "TAKEOVER-8") {
|
|
$message =
|
|
if ($system.WebClientStatus -eq "Not installed") {
|
|
"WebClient not installed, preventing TAKEOVER-8"
|
|
$system.IssuesToCheck = $system.IssuesToCheck | Where-Object { $_ -ne "TAKEOVER-8" }
|
|
}
|
|
elseif ($system.WebClientStatus -eq "Running") {
|
|
"WebClient running, TAKEOVER-8 likely!"
|
|
}
|
|
elseif ($system.WebClientStatus -eq "Installed") {
|
|
"WebClient installed, TAKEOVER-8 possible if it ever starts!"
|
|
}
|
|
else {
|
|
"WebClient check failed, validate TAKEOVER-8 manually"
|
|
}
|
|
$system.Output += "$rolePrefix $message`n"
|
|
}
|
|
}
|
|
|
|
elseif ($role -eq "SMS Provider") {
|
|
|
|
# TAKEOVER-5 is not possible to completely prevent on remote SMS Providers (EPA is not supported by AdminService)
|
|
|
|
# TAKEOVER-6
|
|
Check-IssueStatus -Issue "TAKEOVER-6" `
|
|
-FailedCheckMessage "SMB signing check failed, validate TAKEOVER-6 manually" `
|
|
-LikelyCondition ($system.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, TAKEOVER-6 likely!" `
|
|
-PreventingCondition ($system.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing TAKEOVER-6" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
}
|
|
}
|
|
|
|
# ELEVATE-1 (if no TAKEOVERs exist)
|
|
if ($system.IssuesToCheck -contains "ELEVATE-1" -and ($system.IssuesToCheck -match '^TAKEOVER.*').Count -eq 0) {
|
|
|
|
Check-IssueStatus -Issue "ELEVATE-1" `
|
|
-FailedCheckMessage "SMB signing check failed, validate ELEVATE-1 manually" `
|
|
-LikelyCondition ($system.SMBSigningRequired -eq 0) `
|
|
-LikelyMessage "SMB signing not required, ELEVATE-1 likely!" `
|
|
-PreventingCondition ($system.SMBSigningRequired -eq 1) `
|
|
-PreventingMessage "SMB signing required, preventing ELEVATE-1" `
|
|
-RolePrefix $rolePrefix `
|
|
-System $([ref]$system)
|
|
}
|
|
|
|
# Create some space between sites
|
|
if ($roleCount -eq 0) {
|
|
$system.Output += "$rolePrefix"
|
|
}
|
|
|
|
# Add possible issues to header for system
|
|
$issueText = if ($system.IssuesToCheck.Count -gt 0) { " (Possible Issues: $($system.IssuesToCheck -join ', '))" }
|
|
$systemOutputLines = $system.Output -split "`n"
|
|
$systemOutputLines[0] += $issueText
|
|
$systemOutput = $systemOutputLines -join "`n" # Match the line ending style used in split
|
|
|
|
Write-Host $systemOutput
|
|
}
|
|
|
|
# Process child sites
|
|
$childSiteCount = $childSites.Count
|
|
foreach ($childSite in $childSites) {
|
|
$childSiteCount--
|
|
$newIndent = if ($childSiteCount -eq 0 -and -not ($AllSites | Where-Object { $_.ParentSiteCode -eq $childSite.SiteCode }).Count) { "$Indent " } else { "$Indent │ " }
|
|
Print-SiteStructure -Site $childSite -Indent $newIndent -AllSites $AllSites
|
|
}
|
|
}
|
|
|
|
catch {
|
|
Write-Error "Encountered an unexpected error at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)"
|
|
}
|
|
|
|
}
|
|
|
|
function Print-SMBSigningStatus {
|
|
param(
|
|
$CurrentSiteSystem,
|
|
[string]$Issue
|
|
)
|
|
if ($CurrentSiteSystem.SMBSigningRequired -eq 1) {
|
|
Write-Verbose " SMB signing required: True"
|
|
}
|
|
elseif ($CurrentSiteSystem.SMBSigningRequired -eq 0) {
|
|
Write-Warning " SMB signing required: False ($($Issue) likely!)"
|
|
}
|
|
else {
|
|
Write-Warning " SMB signing required: $($CurrentSiteSystem.SMBSigningRequired) (check $($Issue) manually)"
|
|
}
|
|
}
|
|
|
|
|
|
function Run-Script {
|
|
param (
|
|
[Parameter(Mandatory = $true)]
|
|
[scriptblock]$ScriptBlock,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[array]$ArgumentList,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[int]$TimeoutSeconds
|
|
)
|
|
|
|
try {
|
|
# Start the script block as a job with the arguments
|
|
$job = Start-Job -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList
|
|
|
|
# Wait for the job to finish with the specified timeout
|
|
if ($TimeoutSeconds) {
|
|
$finished = Wait-Job -Job $job -Timeout $TimeoutSeconds
|
|
if (-not $finished) {
|
|
throw
|
|
}
|
|
}
|
|
else {
|
|
Wait-Job -Job $job
|
|
}
|
|
|
|
# Get the result from the job
|
|
$result = Receive-Job -Job $job
|
|
Remove-Job -Job $job
|
|
return $result
|
|
}
|
|
catch {
|
|
return "The operation timed out after $TimeoutSeconds seconds:`n`t$ScriptBlock"
|
|
}
|
|
}
|
|
|
|
# Main
|
|
if ($VerbosePreference -eq "SilentlyContinue") {
|
|
Write-Host "`nCollecting data... this may take a while. Add -Verbose option to show details as they are collected..."
|
|
}
|
|
|
|
# Catch and log unexpected execution error messages
|
|
try {
|
|
# Start the hierarchy output from the CAS
|
|
$namespace = Get-SiteNamespace -SMSProvider $SMSProvider
|
|
$Global:casComputerAccount = $null
|
|
$sites = @()
|
|
$sites = Get-SiteHierarchy -Namespace $namespace -SMSProvider $SMSProvider
|
|
|
|
if ($sites) {
|
|
# Begin output
|
|
Write-Host "`nHierarchy Tree:`n"
|
|
|
|
# Start with top-level site
|
|
$topLevelSites = $sites | Where-Object { -not $_.ParentSiteCode }
|
|
|
|
foreach ($site in $topLevelSites) {
|
|
Print-SiteStructure -Site $site -AllSites $sites
|
|
}
|
|
}
|
|
else {
|
|
Write-Warning "No sites were found. Add -Verbose option to debug"
|
|
}
|
|
}
|
|
|
|
catch {
|
|
Write-Error "Encountered an unexpected error at line $($_.InvocationInfo.ScriptLineNumber): $($_.Exception.Message)"
|
|
}
|
|
|
|
finally {
|
|
# Set preferences back to original values
|
|
$VerbosePreference = $originalVerbosePreference
|
|
$WarningPreference = $originalWarningPreference
|
|
|
|
if ($Host.Name -eq 'ConsoleHost') {
|
|
# For the standard console, we use ConsoleColor enum values
|
|
$Host.PrivateData.VerboseForegroundColor = $originalVerboseColor
|
|
$Host.PrivateData.WarningForegroundColor = $originalWarningColor
|
|
$Host.PrivateData.VerboseBackgroundColor = $originalVerboseBackgroundColor
|
|
$Host.PrivateData.WarningBackgroundColor = $originalWarningBackgroundColor
|
|
} elseif ($Host.Name -eq 'Windows PowerShell ISE Host') {
|
|
# For ISE, we use System.Windows.Media.Color values
|
|
$psISE.Options.VerboseForegroundColor = $originalVerboseColor
|
|
$psISE.Options.WarningForegroundColor = $originalWarningColor
|
|
}
|
|
} |