<# SQLInsight Pro: least-privilege rettigheder til agentens konto på en Windows-server ===================================================================================== Giver SQLInsight-agentens konto præcis den læseadgang, den skal bruge for at overvåge denne server: CPU, hukommelse, diske, tjenester, hændelseslog og planlagte opgaver. Aldrig lokal administrator, og aldrig ret til at køre kode på serveren (heller ikke PowerShell Remoting). Scriptet ændrer kun adgangsrettigheder: det starter, stopper eller ændrer ingen tjenester, opgaver eller indstillinger, og det læser ingen data. Afprøv det først på en server, der ikke er i produktion. Kør som administrator på den server, der skal overvåges (Windows PowerShell 2.0 eller nyere): .\grant-windows-server.ps1 -Account 'DOMÆNE\AGENTSERVER$' -WhatIf # vis, hvad der ville ske; ændrer intet .\grant-windows-server.ps1 -Account 'DOMÆNE\AGENTSERVER$' .\grant-windows-server.ps1 -Account 'DOMÆNE\AGENTSERVER$' -WinRM -EnableFirewall -AgentAddress 10.0.0.15 Fjern præcis det, scriptet har givet, igen: .\grant-windows-server.ps1 -Account 'DOMÆNE\AGENTSERVER$' -Remove Agentens konto: kører agenten som NT SERVICE\SQLInsight Agent, LocalSystem eller NETWORK SERVICE, kommer den til andre servere som computerkontoen DOMÆNE\COMPUTERNAVN$. En gMSA eller domænekonto er den samme overalt. Hvad der gives, og hvorfor: Performance Monitor Users ydelsestællere: CPU, hukommelse, paging Event Log Readers hændelseslogge, der tillader gruppen (System, Application m.fl.; aldrig Security) Distributed COM Users WMI/CIM over DCOM (RPC) WMI: Enable Account + Remote Enable (0x21) på root\cimv2 og root\Microsoft\Windows\TaskScheduler, kun på selve navnerummet (ikke arvet). Ingen skrive- eller metoderet ud over Windows' standard. Service Control Manager forbinde, liste tjenester, læse låsestatus og sikkerhed (CC LC RP RC) - det samme, som en lokalt logget på bruger har. Ingen ret til at starte eller stoppe. Kun med -WinRM læseret (GENERIC_READ, ingen Execute) i WinRM's RootSDDL, så WMI kan læses over WinRM (TCP 5985). Kontoen kommer IKKE i Remote Management Users, for den gruppe giver også PowerShell Remoting. Uden -WinRM bruger agenten DCOM. Hvad der IKKE gives: lokal administrator, PowerShell Remoting/WinRS, ret til at starte/stoppe tjenester, skrive i WMI, læse filer eller Security-loggen. Firewall (kun med -EnableFirewall og -AgentAddress, Windows Server 2012 og nyere): opretter scriptets egne regler "SQLInsight Agent: ..." for RPC (WMI over DCOM og hændelseslog) og med -WinRM for TCP 5985, kun fra agentens adresse. Windows' indbyggede regler røres ikke. Sikkerhedsnet: - Intet ændres, før alt er læst og kontrolleret (konto, grupper, sikkerhed, firewall). Fejler kontrollen, er intet ændret. - Før første ændring gemmes de oprindelige indstillinger (SDDL for Service Control Manager, WMI-navnerummene og WinRM, gruppemedlemskaber og firewallregler) i %ProgramData%\SQLInsight\grant-windows-server\--foer.xml. - Hvad scriptet selv har tilføjet, står i %ProgramData%\SQLInsight\grant-windows-server\.xml (kun administratorer har adgang til mappen). -Remove fjerner kun det; intet, der fandtes før. - Har kontoen allerede en rettighed, røres den ikke (og fjernes ikke med -Remove). Har den en "Nægt"-regel, stopper scriptet uden at ændre noget. - Hvert skridt kontrolleres bagefter. Fejler et skridt, står det, der allerede er gjort, i tilstandsfilen, så scriptet kan køres igen eller med -Remove. - Manuel gendannelse af Service Control Manager: sc.exe sdset scmanager "". #> [CmdletBinding(SupportsShouldProcess = $true)] param( [string] $Account = 'DOMÆNE\AGENTSERVER$', # <== agentens konto [switch] $Remove, [switch] $WinRM, [switch] $EnableFirewall, [string] $AgentAddress ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' $placeholder = 'DOMÆNE\AGENTSERVER$' $whatIf = [bool] $WhatIfPreference # ---- 1. Kontrol: intet ændres, før alt her er i orden --------------------------------------------------------- $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Kør scriptet i en PowerShell, der er startet som administrator ("Kør som administrator"). Intet er ændret.' } if ([string]::IsNullOrEmpty($Account) -or $Account -eq $placeholder) { throw "Angiv agentens konto, fx -Account 'DOMÆNE\AGENTSERVER`$' (SQLInsight Web viser den under serveren). Intet er ændret." } foreach ($c in $Account.ToCharArray()) { if ([char]::IsControl($c)) { throw 'Kontonavnet indeholder et kontroltegn. Intet er ændret.' } } try { $sidObj = (New-Object Security.Principal.NTAccount($Account)).Translate([Security.Principal.SecurityIdentifier]) } catch { throw "Kontoen '$Account' kunne ikke slås op (findes den, og kan serveren nå domænet?). Intet er ændret." } $sid = $sidObj.Value if ($sid -eq 'S-1-5-18') { Write-Host 'Agenten kører som LocalSystem på denne server og har allerede adgang. Intet at gøre.' return } # Grupper med mange medlemmer: rettighederne skal gives til agentens egen konto, aldrig til alle. $broad = @('S-1-1-0', 'S-1-5-7', 'S-1-5-2', 'S-1-5-4', 'S-1-5-11', 'S-1-5-32-544', 'S-1-5-32-545', 'S-1-5-32-546') if (($broad -contains $sid) -or ($sid -match '^S-1-5-21-.+-(512|513|514|515|516|519)$')) { throw "'$Account' er en bred gruppe. Giv rettighederne til agentens egen konto (gMSA, domænekonto eller computerkonto). Intet er ændret." } if ($EnableFirewall -and -not $Remove) { if ([string]::IsNullOrEmpty($AgentAddress)) { throw 'Med -EnableFirewall skal -AgentAddress angives (agentens IP-adresse eller net, fx 10.0.0.15 eller 10.0.0.0/24), så portene kun åbnes for agenten. Intet er ændret.' } foreach ($a in $AgentAddress.Split(',')) { $ip = $null if (-not [Net.IPAddress]::TryParse($a.Trim().Split('/')[0], [ref] $ip)) { throw "-AgentAddress '$a' er ikke en IP-adresse eller et net (fx 10.0.0.15 eller 10.0.0.0/24). Intet er ændret." } } if (-not (Get-Command New-NetFirewallRule -ErrorAction SilentlyContinue)) { throw '-EnableFirewall kræver Windows Server 2012 eller nyere. Åbn portene manuelt (se docs/windows-servere.md). Intet er ændret.' } } elseif ($AgentAddress -and -not $Remove) { Write-Host ' (-AgentAddress bruges kun sammen med -EnableFirewall)' } $stateDir = Join-Path $env:ProgramData 'SQLInsight\grant-windows-server' $statePath = Join-Path $stateDir "$sid.xml" if (Test-Path $statePath) { $state = Import-Clixml $statePath } else { $state = @{ Sid = $sid; Account = $Account; Groups = @(); Wmi = @(); Scm = $false; WinRMRoot = $false; Firewall = @() } } if ($Remove) { $verb = 'Fjerner det, scriptet har givet' } else { $verb = 'Giver rettigheder til' } Write-Host "$verb $Account ($sid) på $env:COMPUTERNAME" if ($Remove -and -not (Test-Path $statePath)) { Write-Host "Intet at fjerne: scriptet har ikke givet $Account noget på denne server ($statePath findes ikke)." return } # ---- Hjælpefunktioner (sikkerhedsbeskrivelser som SDDL, ændret via .NET's RawSecurityDescriptor) --------------- $accessAllowed = [Security.AccessControl.AceQualifier]::AccessAllowed $accessDenied = [Security.AccessControl.AceQualifier]::AccessDenied $allSections = [Security.AccessControl.AccessControlSections]::All function Get-Aces([string] $Sddl) { $raw = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Sddl if ($raw.DiscretionaryAcl -eq $null) { return @() } return @($raw.DiscretionaryAcl | Where-Object { $_ -is [Security.AccessControl.CommonAce] -and $_.SecurityIdentifier -eq $sidObj }) } # Har kontoen en Nægt-regel? Så stopper scriptet: den er sat med vilje. function Test-Denied([string] $Sddl) { return @(Get-Aces $Sddl | Where-Object { $_.AceQualifier -eq $accessDenied }).Count -gt 0 } # Har kontoen allerede (mindst) disse rettigheder fra en Tillad-regel? function Test-Allowed([string] $Sddl, [int] $Mask) { return @(Get-Aces $Sddl | Where-Object { $_.AceQualifier -eq $accessAllowed -and (($_.AccessMask -band $Mask) -eq $Mask) }).Count -gt 0 } # Fjerner præcis én Tillad-regel for kontoen med netop denne maske, ikke arvet og uden flag (den, scriptet satte). function Remove-Ace([string] $Sddl, [int] $Mask) { $raw = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Sddl if ($raw.DiscretionaryAcl -eq $null) { return $Sddl } for ($i = 0; $i -lt $raw.DiscretionaryAcl.Count; $i++) { $ace = $raw.DiscretionaryAcl[$i] if ($ace -is [Security.AccessControl.CommonAce] -and $ace.SecurityIdentifier -eq $sidObj -and $ace.AceQualifier -eq $accessAllowed ` -and $ace.AccessMask -eq $Mask -and $ace.AceFlags -eq [Security.AccessControl.AceFlags]::None) { $raw.DiscretionaryAcl.RemoveAce($i) return $raw.GetSddlForm($allSections) } } return $Sddl } # Indsætter præcis én Tillad-regel (ikke arvet) efter de eksplicitte regler og før de arvede (kanonisk orden). function Add-Ace([string] $Sddl, [int] $Mask) { $raw = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Sddl if ($raw.DiscretionaryAcl -eq $null) { throw 'Sikkerhedsbeskrivelsen har ingen DACL; den ændres ikke.' } $ace = New-Object Security.AccessControl.CommonAce -ArgumentList @([Security.AccessControl.AceFlags]::None, $accessAllowed, $Mask, $sidObj, $false, $null) $at = 0 while ($at -lt $raw.DiscretionaryAcl.Count -and -not $raw.DiscretionaryAcl[$at].IsInherited) { $at++ } $raw.DiscretionaryAcl.InsertAce($at, $ace) $new = $raw.GetSddlForm($allSections) # Kontrol: fjernes den nye regel igen, er resten præcis som før. $before = (New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Sddl).GetSddlForm($allSections) if ((Remove-Ace $new $Mask) -ne $before) { throw 'Intern kontrol fejlede: den nye sikkerhedsbeskrivelse adskiller sig fra den gamle med mere end én regel.' } return $new } function Read-NamespaceSddl([string] $Namespace) { $security = Get-WmiObject -Namespace $Namespace -Class __SystemSecurity $binary = @($null) $rc = $security.PsBase.InvokeMethod('GetSD', $binary) if ($rc -ne 0) { throw "Kunne ikke læse sikkerheden på WMI-navnerummet $Namespace ($rc)." } return (New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList @($binary[0], 0)).GetSddlForm($allSections) } function Write-NamespaceSddl([string] $Namespace, [string] $Sddl) { $raw = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Sddl $bytes = New-Object byte[] $raw.BinaryLength $raw.GetBinaryForm($bytes, 0) $security = Get-WmiObject -Namespace $Namespace -Class __SystemSecurity $rc = $security.PsBase.InvokeMethod('SetSD', @(, $bytes)) if ($rc -ne 0) { throw "Kunne ikke sætte sikkerheden på WMI-navnerummet $Namespace ($rc)." } } function Read-ScmSddl { $sddl = ((& sc.exe sdshow scmanager) | Where-Object { $_ -match '\S' }) -join '' if ($LASTEXITCODE -ne 0 -or $sddl -notmatch '^D:') { throw "Kunne ikke læse Service Control Managers sikkerhed: $sddl" } return $sddl } function Write-ScmSddl([string] $Sddl) { $output = & sc.exe sdset scmanager $Sddl if ($LASTEXITCODE -ne 0) { throw "sc.exe sdset scmanager fejlede ($LASTEXITCODE): $output" } } function Get-GroupMembers($Group) { return @($Group.Invoke('Members') | ForEach-Object { $bytes = $_.GetType().InvokeMember('objectSid', 'GetProperty', $null, $_, $null) (New-Object Security.Principal.SecurityIdentifier($bytes, 0)).Value }) } # Medlemmer tilføjes og fjernes via deres SID (virker også for NT SERVICE\..., gMSA og computerkonti), ellers via navnet. function Set-GroupMember($Group, [bool] $Add) { $paths = @("WinNT://$sid", 'WinNT://' + $Account.Replace('\', '/')) foreach ($path in $paths) { try { if ($Add) { $Group.Add($path) } else { $Group.Remove($path) } return } catch { if ($path -eq $paths[-1]) { throw } } } } function Save-State { if ($whatIf) { return } if (-not (Test-Path $stateDir)) { New-Item -ItemType Directory -Force -Path $stateDir | Out-Null } # Kun administratorer og SYSTEM: filen styrer, hvad -Remove fjerner. $acl = New-Object Security.AccessControl.DirectorySecurity $acl.SetAccessRuleProtection($true, $false) foreach ($who in 'S-1-5-32-544', 'S-1-5-18') { $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule((New-Object Security.Principal.SecurityIdentifier($who)), 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'))) } Set-Acl -Path $stateDir -AclObject $acl $state | Export-Clixml -Path $statePath } # ---- 2. Læs nuværende tilstand og læg en plan (ændrer intet) --------------------------------------------------- $wmiMask = 0x21 # Enable Account (0x1) + Remote Enable (0x20) $scmMask = 0x20015 # CC (0x1) + LC (0x4) + RP (0x10) + RC (READ_CONTROL 0x20000) $winrmMask = [int] 0x80000000 # GENERIC_READ (GR), ingen Execute (GX) $plan = New-Object Collections.ArrayList $original = @{ Time = (Get-Date).ToString('o'); Account = $Account; Sid = $sid; Groups = @{}; Wmi = @{}; Scm = $null; WinRMRoot = $null; Firewall = @{} } # Lokale grupper, fundet via deres SID, så det virker på dansk, engelsk og andre sprog. $groups = @( @('S-1-5-32-558', 'ydelsestællere (CPU, hukommelse, paging)'), @('S-1-5-32-573', 'hændelseslogge (aldrig Security)'), @('S-1-5-32-562', 'WMI over DCOM') ) foreach ($g in $groups) { try { $groupName = (New-Object Security.Principal.SecurityIdentifier($g[0])).Translate([Security.Principal.NTAccount]).Value.Split('\')[1] } catch { Write-Host " - gruppen $($g[0]) ($($g[1])) findes ikke på denne Windows-version; springes over" continue } $group = [ADSI]("WinNT://$env:COMPUTERNAME/$groupName,group") $isMember = (Get-GroupMembers $group) -contains $sid $original.Groups[$g[0]] = $isMember $added = @($state.Groups) -contains $g[0] if ($Remove) { if ($added -and $isMember) { [void] $plan.Add(@{ Kind = 'GroupRemove'; Sid = $g[0]; Name = $groupName; Text = "fjern fra gruppen $groupName" }) } elseif ($added) { $state.Groups = @($state.Groups | Where-Object { $_ -ne $g[0] }); Write-Host " = ${groupName}: allerede fjernet" } elseif ($isMember) { Write-Host " = ${groupName}: var medlem før scriptet; bevares" } } elseif ($isMember) { Write-Host " = ${groupName}: allerede medlem" } else { [void] $plan.Add(@{ Kind = 'GroupAdd'; Sid = $g[0]; Name = $groupName; Text = "medlem af $groupName ($($g[1]))" }) } } # WMI-navnerum. $namespaces = @('root\cimv2') if (Get-WmiObject -Namespace 'root\Microsoft\Windows' -Class __Namespace -Filter "Name='TaskScheduler'" -ErrorAction SilentlyContinue) { $namespaces += 'root\Microsoft\Windows\TaskScheduler' } elseif (-not $Remove) { Write-Host ' - root\Microsoft\Windows\TaskScheduler findes ikke (før Windows Server 2012): planlagte opgaver kan ikke måles' } foreach ($ns in $namespaces) { $sddl = Read-NamespaceSddl $ns $original.Wmi[$ns] = $sddl if ($Remove) { if (@($state.Wmi) -contains $ns) { [void] $plan.Add(@{ Kind = 'WmiRemove'; Namespace = $ns; Text = "fjern WMI-adgang på $ns" }) } } elseif (Test-Denied $sddl) { throw "Kontoen har en Nægt-regel på WMI-navnerummet $ns. Den er sat med vilje; fjern den først, hvis agenten skal læse herfra. Intet er ændret." } elseif (Test-Allowed $sddl $wmiMask) { Write-Host " = WMI ${ns}: har allerede adgang" } else { [void] (Add-Ace $sddl $wmiMask) # kontrollerer, at ændringen er præcis én regel [void] $plan.Add(@{ Kind = 'WmiAdd'; Namespace = $ns; Text = "WMI $ns (Enable Account, Remote Enable)" }) } } # Service Control Manager: over netværket må ellers kun administratorer liste tjenester. $scm = Read-ScmSddl $original.Scm = $scm if ($Remove) { if ($state.Scm) { [void] $plan.Add(@{ Kind = 'ScmRemove'; Text = 'fjern læseadgang til Service Control Manager' }) } } elseif (Test-Denied $scm) { throw 'Kontoen har en Nægt-regel på Service Control Manager. Den er sat med vilje; fjern den først, hvis agenten skal se tjenester. Intet er ændret.' } elseif (Test-Allowed $scm $scmMask) { Write-Host ' = Service Control Manager: har allerede læseadgang' } else { [void] (Add-Ace $scm $scmMask) [void] $plan.Add(@{ Kind = 'ScmAdd'; Text = 'Service Control Manager (CC LC RP RC: forbinde, liste, læse)' }) } # WinRM (kun med -WinRM, eller når -Remove skal fjerne det, scriptet gav). if (($WinRM -and -not $Remove) -or ($Remove -and $state.WinRMRoot)) { $service = Get-Service -Name WinRM -ErrorAction SilentlyContinue if (-not $service -or $service.Status -ne 'Running') { throw 'WinRM-tjenesten kører ikke. Start den (winrm quickconfig), eller kør uden -WinRM, så agenten bruger DCOM. Intet er ændret.' } $root = (Get-Item WSMan:\localhost\Service\RootSDDL).Value $original.WinRMRoot = $root if ($Remove) { [void] $plan.Add(@{ Kind = 'WinRMRemove'; Text = 'fjern læseadgang i WinRM (RootSDDL)' }) } elseif (Test-Denied $root) { throw 'Kontoen har en Nægt-regel i WinRM (RootSDDL). Kør uden -WinRM, så agenten bruger DCOM. Intet er ændret.' } elseif (Test-Allowed $root $winrmMask) { Write-Host ' = WinRM: har allerede læseadgang' } else { [void] (Add-Ace $root $winrmMask) [void] $plan.Add(@{ Kind = 'WinRMAdd'; Text = 'WinRM (RootSDDL): læseret (GR), ingen Execute' }) } } # Firewall: scriptets egne regler, kun fra agentens adresse. Windows' indbyggede regler røres ikke. $rules = @( @{ Name = 'SQLInsight-Agent-RPC-EPMAP'; Display = 'SQLInsight Agent: RPC Endpoint Mapper (WMI over DCOM, hændelseslog)'; Port = 'RPCEPMap'; Service = 'rpcss' }, @{ Name = 'SQLInsight-Agent-WMI-DCOM'; Display = 'SQLInsight Agent: WMI over DCOM'; Port = 'RPC'; Service = 'winmgmt' }, @{ Name = 'SQLInsight-Agent-EventLog'; Display = 'SQLInsight Agent: hændelseslog (RPC)'; Port = 'RPC'; Service = 'eventlog' } ) if ($WinRM) { $rules += @{ Name = 'SQLInsight-Agent-WinRM'; Display = 'SQLInsight Agent: WinRM (HTTP 5985)'; Port = '5985'; Service = $null } } if ($Remove -and @($state.Firewall).Count -gt 0 -and (Get-Command Get-NetFirewallRule -ErrorAction SilentlyContinue)) { foreach ($name in @($state.Firewall)) { $existing = Get-NetFirewallRule -Name $name -ErrorAction SilentlyContinue if ($existing) { $original.Firewall[$name] = @(($existing | Get-NetFirewallAddressFilter).RemoteAddress) } if ($existing) { [void] $plan.Add(@{ Kind = 'FirewallRemove'; Name = $name; Text = "fjern firewallreglen $name" }) } } } elseif ($EnableFirewall -and -not $Remove) { $wanted = @($AgentAddress.Split(',') | ForEach-Object { $_.Trim() }) foreach ($r in $rules) { $existing = Get-NetFirewallRule -Name $r.Name -ErrorAction SilentlyContinue if ($existing) { $current = @(($existing | Get-NetFirewallAddressFilter).RemoteAddress) $original.Firewall[$r.Name] = $current if (($current -join ',') -eq ($wanted -join ',')) { Write-Host " = firewall $($r.Name): uændret"; continue } [void] $plan.Add(@{ Kind = 'FirewallUpdate'; Rule = $r; Address = $wanted; Text = "firewallreglen $($r.Name): kun fra $AgentAddress" }) } else { [void] $plan.Add(@{ Kind = 'FirewallAdd'; Rule = $r; Address = $wanted; Text = "firewall: $($r.Display) (TCP $($r.Port)), kun fra $AgentAddress" }) } } } if ($plan.Count -eq 0) { if ($Remove -and -not $whatIf) { Save-State } Write-Host 'Alt er allerede på plads. Intet er ændret.' return } # ---- 3. Gem det oprindelige, og udfør planen ét skridt ad gangen ----------------------------------------------- $backupPath = Join-Path $stateDir ("$sid-" + (Get-Date).ToString('yyyyMMdd-HHmmss') + '-foer.xml') if (-not $whatIf) { Save-State $original | Export-Clixml -Path $backupPath Write-Host " Oprindelige indstillinger gemt i $backupPath" } foreach ($step in $plan) { if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, $step.Text)) { continue } try { switch ($step.Kind) { 'GroupAdd' { $group = [ADSI]("WinNT://$env:COMPUTERNAME/$($step.Name),group") Set-GroupMember $group $true if (-not ((Get-GroupMembers $group) -contains $sid)) { throw "kontoen kom ikke i gruppen $($step.Name)" } $state.Groups = @($state.Groups) + $step.Sid } 'GroupRemove' { $group = [ADSI]("WinNT://$env:COMPUTERNAME/$($step.Name),group") Set-GroupMember $group $false $state.Groups = @($state.Groups | Where-Object { $_ -ne $step.Sid }) } 'WmiAdd' { Write-NamespaceSddl $step.Namespace (Add-Ace (Read-NamespaceSddl $step.Namespace) $wmiMask) if (-not (Test-Allowed (Read-NamespaceSddl $step.Namespace) $wmiMask)) { throw "adgangen på $($step.Namespace) blev ikke sat" } $state.Wmi = @($state.Wmi) + $step.Namespace } 'WmiRemove' { $sddl = Read-NamespaceSddl $step.Namespace $new = Remove-Ace $sddl $wmiMask if ($new -ne $sddl) { Write-NamespaceSddl $step.Namespace $new } $state.Wmi = @($state.Wmi | Where-Object { $_ -ne $step.Namespace }) } 'ScmAdd' { Write-ScmSddl (Add-Ace (Read-ScmSddl) $scmMask) if (-not (Test-Allowed (Read-ScmSddl) $scmMask)) { throw 'læseadgangen til Service Control Manager blev ikke sat' } $state.Scm = $true } 'ScmRemove' { $sddl = Read-ScmSddl $new = Remove-Ace $sddl $scmMask if ($new -ne $sddl) { Write-ScmSddl $new } $state.Scm = $false } 'WinRMAdd' { $new = Add-Ace (Get-Item WSMan:\localhost\Service\RootSDDL).Value $winrmMask Set-Item -Path WSMan:\localhost\Service\RootSDDL -Value $new -Force -WhatIf:$false -Confirm:$false if (-not (Test-Allowed (Get-Item WSMan:\localhost\Service\RootSDDL).Value $winrmMask)) { throw 'læseretten i WinRM blev ikke sat' } $state.WinRMRoot = $true } 'WinRMRemove' { $sddl = (Get-Item WSMan:\localhost\Service\RootSDDL).Value $new = Remove-Ace $sddl $winrmMask if ($new -ne $sddl) { Set-Item -Path WSMan:\localhost\Service\RootSDDL -Value $new -Force -WhatIf:$false -Confirm:$false } $state.WinRMRoot = $false } 'FirewallAdd' { $r = $step.Rule $arguments = @{ Name = $r.Name; DisplayName = $r.Display; Direction = 'Inbound'; Action = 'Allow'; Protocol = 'TCP'; LocalPort = $r.Port; RemoteAddress = $step.Address; Profile = 'Any' } if ($r.Service) { $arguments.Program = '%SystemRoot%\system32\svchost.exe'; $arguments.Service = $r.Service } New-NetFirewallRule @arguments -WhatIf:$false -Confirm:$false | Out-Null $state.Firewall = @($state.Firewall) + $r.Name } 'FirewallUpdate' { Set-NetFirewallRule -Name $step.Rule.Name -RemoteAddress $step.Address -WhatIf:$false -Confirm:$false if (-not (@($state.Firewall) -contains $step.Rule.Name)) { $state.Firewall = @($state.Firewall) + $step.Rule.Name } } 'FirewallRemove' { Remove-NetFirewallRule -Name $step.Name -WhatIf:$false -Confirm:$false $state.Firewall = @($state.Firewall | Where-Object { $_ -ne $step.Name }) } } } catch { Save-State throw "Fejlede ved '$($step.Text)': $($_.Exception.Message) Det, der allerede er gjort, står i $statePath; ret fejlen og kør scriptet igen, eller kør det med -Remove. De oprindelige indstillinger står i $backupPath." } Save-State if ($Remove) { Write-Host " - $($step.Text)" } else { Write-Host " + $($step.Text)" } } if ($whatIf) { return } if ($Remove) { $left = @($state.Groups).Count + @($state.Wmi).Count + @($state.Firewall).Count if ($state.Scm) { $left++ } if ($state.WinRMRoot) { $left++ } if ($left -eq 0) { Move-Item -Path $statePath -Destination (Join-Path $stateDir ("$sid-" + (Get-Date).ToString('yyyyMMdd-HHmmss') + '-fjernet.xml')) } Write-Host 'Færdig. Det, scriptet gav, er fjernet; det, der fandtes før, er bevaret.' } else { Write-Host 'Færdig. Test forbindelsen fra SQLInsight (Servere > Windows-servere > serveren > Test forbindelsen).' }