Indented.SecurityPolicy.psm1
using namespace Indented.SecurityPolicy using namespace System.Collections.Generic using namespace System.Management.Automation using namespace System.Security.Principal enum AllocateDASD { Administrators AdministratorsAndPowerUsers AdministratorsAndInteractiveUsers } enum AuditNTLMInDomain { Disable = 0 EnableForDomainAccountsToDomainServers = 1 EnableForDomainAccounts = 3 EnableForDomainServers = 5 EnableAll = 7 } enum AuditReceivingNTLMTraffic { Disable EnableForDomainAccounts EnableForAllAccounts } enum ConsentPromptBehaviorAdmin { NoPrompting PromptForCredentialsOnSecureDesktop PromptForConsentOnSecureDesktop PromptForCredentials PromptForConsent PromptForConsentForNonWindowsBinaries } enum ConsentPromptBehaviorUser { AutomaticallyDeny = 0 PromptForCredentialsOnSecureDesktop = 1 PromptForCredentials = 3 } enum DontDisplayLockedUserId { All DisplayNameOnly None } enum ForceGuest { Classic GuestOnly } enum ForceKeyProtection { UserInputNotRequired PromptOnFirstUse PromptAlways } enum LdapClientIntegrity { None NegotiateSigning RequireSigning } enum LdapServerIntegrity { None RequireSigning } enum LmCompatibilityLevel { SendLMAndNTLMResponses SendLMAndNTLM SendNTLMResponseOnly SendNTLMv2ResponseOnly SendNTLMv2ResponseOnlyRefuseLM SendNTLMv2ResponseOnlyRefuseLMAndNTLM } enum NoConnectedUser { Disabled = 0 DenyAdd = 1 DenyAddAndLogon = 3 } [Flags()] enum NTLMMinSec { RequireNTLMv2SessionSecurity = 524288 Require128BitEncryption = 536870912 } enum RestrictNTLMInDomain { Disable = 0 DenyDomainAccountsToDomainServers = 1 DenyDomainAccounts = 3 DenyDomainServers = 5 DenyAll = 7 } enum RestrictReceivingNTLMTraffic { AllowAll DenyDomainAccounts DenyAll } enum RestrictSendingNTLMTraffic { AllowAll AuditAll DenyAll } enum ScRemoveOption { NoAction LockWorkstation ForceLogoff Disconnect } enum SmbServerNameHardeningLevel { Off Accept Required } [Flags()] enum SupportedEncryptionTypes { DES_CBC_CRC = 1 DES_CBC_MD5 = 2 RC4_HMAC_MD5 = 4 AES128_HMAC_SHA1 = 8 AES256_HMAC_SHA1 = 16 Future = 2147483616 } enum Enabled { Disabled Enabled } enum Ensure { Absent Present } enum RegistryValueType { DWord QWord String MultiString ExpandString Binary } class AccountBase { [WellKnownSidType]$SidType [SecurityIdentifier]$MachineSid = (GetMachineSid) [SecurityIdentifier] GetSid() { $domainRole = (Get-CimInstance Win32_ComputerSystem -Property DomainRole).DomainRole if ($domainRole -in 4, 5) { $searcher = [ADSISearcher]'(objectClass=domainDNS)' $null = $searcher.PropertiesToLoad.Add('objectSID') $domainSid = [SecurityIdentifier]::new($searcher.FindOne().Properties['objectSID'][0], 0) return [SecurityIdentifier]::new($this.SidType, $domainSid) } else { return [SecurityIdentifier]::new($this.SidType, $this.MachineSid) } } } class AccountStatus : AccountBase { [Enabled]$Value [AccountStatus] Get() { $localUser = Get-LocalUser -Sid $this.GetSid() $this.Value = [Enabled][Int]$localUser.Enabled return $this } [Void] Set() { if ([Boolean][Int]$this.Value) { Enable-LocalUser -Sid $this.GetSid() } else { Disable-LocalUser -Sid $this.GetSid() } } [Boolean] Test() { $localUser = Get-LocalUser -Sid $this.GetSid() return $localUser.Enabled -eq ([Boolean][Int]$this.Value) } } class RenameAccount : AccountBase { [String]$Value [RenameAccount] Get() { $this.Value = (Get-LocalUser -Sid $this.GetSid()).Name return $this } [Void] Set() { Rename-LocalUser -Sid $this.GetSid() -NewName $this.Value } [Boolean] Test() { $localUser = Get-LocalUser -Sid $this.GetSid() return $localUser.Name -eq $this.Value } } class AccountStatusAdministrator : AccountStatus { [WellKnownSidType]$SidType = 'AccountAdministratorSid' } class AccountStatusGuest : AccountStatus { [WellKnownSidType]$SidType = 'AccountGuestSid' } [DscResource()] class GroupManagedServiceAccount { [DscProperty()] [Ensure]$Ensure = 'Present' [DscProperty(Key)] [String]$Name [GroupManagedServiceAccount] Get() { if (Test-GroupManagedServiceAccount -AccountName $this.Name) { $this.Ensure = 'Present' } else { $this.Ensure = 'Absent' } return $this } [Void] Set() { if ($this.Ensure -eq 'Present' -and -not (Test-GroupManagedServiceAccount -AccountName $this.Name)) { Install-GroupManagedServiceAccount -AccountName $this.Name } elseif ($this.Ensure -eq 'Absent' -and (Test-GroupManagedServiceAccount -AccountName $this.Name)) { Uninstall-GroupManagedServiceAccount -AccountName $this.Name } } [Boolean] Test() { if ($this.Ensure -eq 'Present') { return Test-GroupManagedServiceAccount -AccountName $this.Name } elseif ($this.Ensure -eq 'Absent') { return -not (Test-GroupManagedServiceAccount -AccountName $this.Name) } return $true } } [DscResource()] class RegistryPolicy { [DscProperty()] [Ensure]$Ensure = 'Present' [DscProperty(Key)] [String]$Name [DscProperty(Key)] [String]$Path [DscProperty()] [String[]]$Data = @() [DscProperty()] [RegistryValueType]$ValueType = 'String' Hidden [Object] $ParsedData Hidden [Boolean] CompareValue() { $value = Get-ItemPropertyValue -Path $this.Path -Name $this.Name if ($this.ValueType -eq 'MultiString') { if (Compare-Object @($this.ParsedData) @($value) -SyncWindow 0) { return $false } } elseif ($value -ne $this.ParsedData) { return $false } return $true } Hidden [Void] ParseData() { try { $this.ParsedData = switch ($this.ValueType) { 'DWord' { [UInt32]::Parse($this.Data[0]); break } 'QWord' { [UInt64]::Parse($this.Data[0]); break } 'MultiString' { $this.Data; break } 'Binary' { foreach ($value in $this.Data -split ' ') { [Convert]::ToByte($value, 16) } break } default { $this.Data[0] } } } catch { throw } } [RegistryPolicy] Get() { $this.ParseData() if (-not $this.Test()) { $this.Ensure = $this.Ensure -bxor 1 } return $this } [Void] Set() { $this.ParseData() $params = @{ Name = $this.Name Path = $this.Path } if ($this.Ensure -eq 'Present') { if (Test-Path $this.Path) { $key = Get-Item $this.Path } else { $key = New-Item $this.Path -ItemType Key -Force } if ($this.Name -in $key.GetValueNames()) { if ($key.GetValueKind($this.Name).ToString() -eq $this.ValueType) { if (-not $this.CompareValue()) { Set-ItemProperty -Value $this.ParsedData @params } } else { Remove-ItemProperty @params New-ItemProperty -PropertyType $this.ValueType -Value $this.ParsedData @params } } else { New-ItemProperty -PropertyType $this.ValueType -Value $this.ParsedData @params } } elseif ($this.Ensure -eq 'Absent') { if (Test-Path $this.Path) { $key = Get-Item $this.Path if ($this.Name -in $key.GetValueNames()) { Remove-ItemProperty @params } } } } [Boolean] Test() { $this.ParseData() if ($this.Ensure -eq 'Present') { if (-not (Test-Path $this.Path)) { return $false } $key = Get-Item $this.Path if ($key.GetValueNames() -notcontains $this.Name) { return $false } if ($key.GetValueKind($this.Name).ToString() -ne $this.ValueType) { return $false } return $this.CompareValue() } elseif ($this.Ensure -eq 'Absent') { if (Test-Path $this.Path) { $key = Get-Item $this.Path if ($key.GetValueNames() -contains $this.Name) { return $false } } } return $true } } class RenameAccountAdministrator : RenameAccount { [WellKnownSidType]$SidType = 'AccountAdministratorSid' } class RenameAccountGuest : RenameAccount { [WellKnownSidType]$SidType = 'AccountGuestSid' } [DscResource()] class SecurityOption { [DscProperty()] [Ensure]$Ensure = 'Present' [DscProperty(Key)] [String]$Name [DscProperty()] [String[]]$Value [DscProperty(NotConfigurable)] [String]$Description [Object]$ParsedValue Hidden [Void] ParseValue() { $securityOptionInfo = Resolve-SecurityOption $this.Name $valueType = $securityOptionInfo.ValueType -as [Type] $candidateValue = $this.Value if ($valueType.BaseType -ne [Array]) { $candidateValue = $this.Value[0] } if ($valueType.BaseType -eq [Enum]) { $enumValue = 0 -as $valueType if ($valueType::TryParse([String]$candidateValue, $true, [Ref]$enumValue)) { $this.ParsedValue = $enumValue } } else { $this.ParsedValue = $candidateValue -as $securityOptionInfo.ValueType } } Hidden [Boolean] CompareValue([Object]$ReferenceValue, [Object]$DifferenceValue) { if ($ReferenceValue -is [Array] -or $DifferenceValue -is [Array]) { return ($ReferenceValue -join ' ') -eq ($DifferenceValue -join ' ') } else { return $ReferenceValue -eq $DifferenceValue } } [SecurityOption] Get() { $securityOption = Get-SecurityOption -Name $this.Name $this.Name = $securityOption.Name $this.Value = $securityOption.Value $this.Description = $securityOption.Description return $this } [Void] Set() { if ($this.Ensure -eq 'Present') { $this.ParseValue() Set-SecurityOption -Name $this.Name -Value $this.ParsedValue } elseif ($this.Ensure -eq 'Absent') { Reset-SecurityOption -Name $this.Name } } [Boolean] Test() { $securityOption = Get-SecurityOption -Name $this.Name if ($this.Ensure -eq 'Present') { $this.ParseValue() return $this.CompareValue($this.ParsedValue, $securityOption.Value) } elseif ($this.Ensure -eq 'Absent') { $securityOptionInfo = Resolve-SecurityOption $this.Name return $this.CompareValue($securityOptionInfo.Default, $securityOption.Value) } return $true } } [DscResource()] class UserRightAssignment { [DscProperty()] [Ensure]$Ensure = 'Present' [DscProperty(Key)] [String]$Name [DscProperty()] [String[]]$AccountName [DscProperty()] [Boolean]$Replace [DscProperty(NotConfigurable)] [String]$Description Hidden [SecurityIdentifier[]]$requestedIdentities Hidden [SecurityIdentifier[]]$currentIdentities Hidden [Void] InitializeRequest() { try { $userRight = Resolve-UserRight $this.Name if (@($userRight).Count -ne 1) { throw 'The requested user right is ambiguous, matched right names: {0}' -f ($userRight.UserRight -join ', ') } $this.Name = $userRight.Name $this.Description = $userRight.Description } catch { throw } if ($this.Ensure -eq 'Present' -and $this.AccountName.Count -eq 0) { throw 'Invalid request. AccountName cannot be empty when ensuring a right is present.' } if ($this.Ensure -eq 'Absent' -and $this.Replace) { throw 'Replace may only be set when ensuring a set of accounts is present.' } $this.requestedIdentities = foreach ($identity in $this.AccountName) { ([NTAccount]$identity).Translate([SecurityIdentifier]) } $this.currentIdentities = foreach ($identity in (Get-UserRight -Name $this.Name).AccountName) { if ($identity -is [NTAccount]) { $identity.Translate([SecurityIdentifier]) } else { $identity } } } Hidden [Boolean] CompareAccountNames() { return [Boolean](-not (Compare-Object @($this.requestedIdentities) @($this.currentIdentities))) } Hidden [Boolean] IsAssignedRight([String]$Identity) { return $this.currentIdentities -contains ([NTAccount]$Identity).Translate([SecurityIdentifier]) } [UserRightAssignment] Get() { try { $this.InitializeRequest() $this.AccountName = (Get-UserRight -Name $this.Name).AccountName return $this } catch { throw } } [Void] Set() { try { $this.InitializeRequest() if ($this.Ensure -eq 'Present') { if ($this.Replace) { Set-UserRight -Name $this.Name -AccountName $this.AccountName } else { foreach ($identity in $this.AccountName) { if (-not $this.IsAssignedRight($identity)) { Grant-UserRight -Name $this.Name -AccountName $identity } } } } elseif ($this.Ensure -eq 'Absent') { if ($this.AccountName.Count -eq 0 -and $this.currentIdentities.Count -gt 0) { Clear-UserRight -Name $this.Name } elseif ($this.AccountName -gt 0) { foreach ($identity in $this.AccountName) { if ($this.IsAssignedRight($identity)) { Revoke-UserRight -Name $this.Name -AccountName $identity } } } } } catch { throw } } [Boolean] Test() { try { $this.InitializeRequest() $userRight = Get-UserRight -Name $this.Name if ($this.Ensure -eq 'Present') { if ($this.Replace) { if ($this.currentIdentities.Count -eq 0) { return $false } return $this.CompareAccountNames() } else { foreach ($identity in $this.AccountName) { if (-not $this.IsAssignedRight($identity)) { return $false } } } } elseif ($this.Ensure -eq 'Absent') { if ($this.AccountName.Count -eq 0 -and $userRight.AccountName) { return $false } elseif ($this.AccountName.Count -gt 0) { foreach ($identity in $this.AccountName) { if ($this.IsAssignedRight($identity)) { return $false } } } } return $true } catch { throw } } } function CloseLsaPolicy { <# .SYNOPSIS Close the LSA policy handle if it is open. .DESCRIPTION Close the LSA policy handle if it is open. #> param ( [AllowNull()] [Object]$lsa ) if ($lsa) { $lsa.Dispose() } } function GetMachineSid { [OutputType([System.Security.Principal.SecurityIdentifier])] param ( ) [Indented.SecurityPolicy.Account]::LookupAccountName($env:COMPUTERNAME, $env:COMPUTERNAME) } function GetSecurityOptionData { param ( [String]$Name ) if ($Script:securityOptionData.Contains($Name)) { $securityOptionData = $Script:securityOptionData[$Name] if (-not $securityOptionData.Name) { $securityOptionData.Name = $Name } $securityOptionData } else { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new('{0} is an invalid security option name' -f $Name), 'InvalidSecurityOptionName', 'InvalidArgument', $Name ) throw $errorRecord } } function ImportSecurityOptionData { $localizedSecurityOptions = Import-LocalizedData -FileName securityOptions $path = Join-Path $myinvocation.MyCommand.Module.ModuleBase 'data\securityOptions.psd1' $Script:securityOptionData = Import-PowerShellDataFile $path # Create the lookup helper $Script:securityOptionLookupHelper = @{} # Merge localized descriptions and fill the helper foreach ($key in [String[]]$Script:securityOptionData.Keys) { $value = $Script:securityOptionData[$key] $description = $localizedSecurityOptions[$key] $category, $shortDescription = $description -split ': *', 2 $value.Add('Category', $category) $value.Add('Description', $description) $value.Add('ShortDescription', $shortDescription) $value.Add('PSTypeName', 'Indented.SecurityPolicy.SecurityOptionInfo') if (-not $value.Contains('Name')) { $value.Add('Name', $key) } $value = [PSCustomObject]$value $Script:securityOptionData[$key] = $value $Script:securityOptionLookupHelper.Add($description, $key) } } function ImportUserRightData { $localizedUserRights = Import-LocalizedData -FileName userRights $Script:userRightData = @{} $Script:userRightLookupHelper = @{} foreach ($key in $localizedUserRights.Keys) { $value = [PSCustomObject]@{ Name = [UserRight]$key Description = $localizedUserRights[$key] PSTypeName = 'Indented.SecurityPolicy.UserRightInfo' } $Script:userRightData.Add($key, $value) $Script:userRightLookupHelper.Add($value.Description, $key) } } function NewImplementingType { param ( [String]$Name ) ($Name -as [Type])::new() } function OpenLsaPolicy { <# .SYNOPSIS Open the LSA policy handle. .DESCRIPTION Open the LSA policy handle. #> try { return [Indented.SecurityPolicy.Lsa]::new() } catch { $innerException = $_.Exception.GetBaseException() if ($innerException -is [UnauthorizedAccessException]) { $exception = [UnauthorizedAccessException]::new('Cannot open LSA: Access denied', $innerException) $category = 'PermissionDenied' } else { $exception = [InvalidOperationException]::new('An error occurred when opening the LSA', $innerException) $category = 'OperationStopped' } $errorRecord = [ErrorRecord]::new( $exception, 'CannotOpenLSA', $category, $null ) throw $errorRecord } } function Clear-UserRight { <# .SYNOPSIS Clears the accounts from the specified user right. .DESCRIPTION Clears the accounts from the specified user right. .EXAMPLE Clear-UserRight 'Log on as a batch job' Clear the SeBatchLogonRight right. #> [CmdletBinding()] param ( # The name of the user right to clear. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateScript( { $_ | Resolve-UserRight } )] [Alias('UserRight')] [String[]]$Name ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { foreach ($userRight in $Name | Resolve-UserRight) { try { foreach ($identity in $lsa.EnumerateAccountsWithUserRight($userRight.Name)) { $lsa.RemoveAccountRights($identity, [UserRight[]]$userRight.Name) } } catch { Write-Error -ErrorRecord $_ } } } end { CloseLsaPolicy $lsa } } function Get-AssignedUserRight { <# .SYNOPSIS Gets all user rights granted to a principal. .DESCRIPTION Get a list of all the user rights granted to one or more principals. Does not expand group membership. .EXAMPLE Get-AssignedUserRight Returns a list of all defined for the current user. .EXAMPLE Get-AssignedUserRight Administrator Get the list of rights assigned to the administrator account. #> [CmdletBinding()] [OutputType('Indented.SecurityPolicy.AssignedUserRight')] param ( # Find rights for the specified account names. By default AccountName is the current user. [Parameter(Position = 1, ValueFromPipelineByPropertyName, ValueFromPipeline)] [String[]]$AccountName = $env:USERNAME ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { foreach ($account in $AccountName) { try { [PSCustomObject]@{ AccountName = $account Name = $lsa.EnumerateAccountRights($account) PSTypeName = 'Indented.SecurityPolicy.AssignedUserRight' } } catch { Write-Error -ErrorRecord $_ } } } end { CloseLsaPolicy $lsa } } filter Get-SecurityOption { <# .SYNOPSIS Get the value of a security option. .DESCRIPTION Get the value of a security option. .EXAMPLE Get-SecurityOption 'Accounts: Administrator account status' Gets the current value of the administrator account status policy (determined by the state of the account). .EXAMPLE Get-SecurityOption 'EnableLUA' Get the value of the "User Account Control: Run all administrators in Admin Approval Mode" policy. #> [CmdletBinding()] param ( # The name of the security option to set. [Parameter(Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]]$Name ) foreach ($securityOptionInfo in $Name | Resolve-SecurityOption | Sort-Object Category, ShortDescription) { try { $value = $securityOptionInfo.Default if ($securityOptionInfo.Key) { Write-Debug ('Registry value type: {0}' -f $securityOption.ValueName) if (Test-Path $securityOptionInfo.Key) { $key = Get-Item $securityOptionInfo.Key -ErrorAction Stop if ($key.GetValueNames() -contains $securityOptionInfo.Name) { $value = Get-ItemPropertyValue -Path $securityOptionInfo.Key -Name $securityOptionInfo.Name -ErrorAction Stop } } } else { Write-Debug ('Class-handled value type: {0}' -f $securityOptionInfo.Name) $class = NewImplementingType $securityOptionInfo.Class $value = $class.Get().Value } if ($value -ne 'Not Defined') { $value = $value -as ($securityOptionInfo.ValueType -as [Type]) } [PSCustomObject]@{ Name = $securityOptionInfo.Name Description = $securityOptionInfo.Name Value = $value Category = $securityOptionInfo.Category ShortDescription = $securityOptionInfo.ShortDescription PSTypeName = 'Indented.SecurityPolicy.SecurityOptionSetting' } } catch { $innerException = $_.Exception.GetBaseException() $errorRecord = [ErrorRecord]::new( [InvalidOperationException]::new( ('An error occurred retrieving the security option {0}: {1}' -f $securityOptionInfo.ValueName, $innerException.Message), $innerException ), 'FailedToRetrieveSecurityOptionSetting', 'OperationStopped', $null ) Write-Error -ErrorRecord $errorRecord } } } function Get-UserRight { <# .SYNOPSIS Gets all accounts that are assigned a specified user right. .DESCRIPTION Gets a list of all accounts that hold a specified user right. Group membership is not evaluated, the values returned are explicitly listed under the specified user rights. .EXAMPLE Get-UserRight SeServiceLogonRight Returns a list of all accounts that hold the "Log on as a service" right. .EXAMPLE Get-UserRight SeServiceLogonRight, SeDebugPrivilege Returns accounts with the SeServiceLogonRight and SeDebugPrivilege rights. #> [CmdletBinding()] [OutputType('Indented.SecurityPolicy.UserRight')] param ( # The user right, or rights, to query. [Parameter(Position = 1, ValueFromPipelineByPropertyName, ValueFromPipeline)] [ValidateScript( { $_ | Resolve-UserRight } )] [Alias('UserRight')] [String[]]$Name ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { foreach ($userRight in $Name | Resolve-UserRight) { try { [PSCustomObject]@{ Name = $userRight.Name Description = $userRight.Description AccountName = $lsa.EnumerateAccountsWithUserRight($userRight.Name) PSTypeName = 'Indented.SecurityPolicy.UserRightSetting' } } catch { $innerException = $_.Exception.GetBaseException() $errorRecord = [ErrorRecord]::new( [InvalidOperationException]::new( ('An error occurred retrieving the user right {0}: {1}' -f $userRight.Name, $innerException.Message), $innerException ), 'FailedToRetrieveUserRight', 'OperationStopped', $null ) Write-Error -ErrorRecord $errorRecord } } } end { CloseLsaPolicy $lsa } } function Grant-UserRight { <# .SYNOPSIS Grant rights to an account. .DESCRIPTION Grants one or more rights to the specified accounts. .EXAMPLE Grant-UserRight -Name 'Allow logon locally' -AccountName 'Administrators' Grants the allow logon locally right to the Administrators group. #> [CmdletBinding(SupportsShouldProcess)] param ( # The user right, or rights, to query. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [ValidateScript( { $_ | Resolve-UserRight } )] [Alias('UserRight')] [String[]]$Name, # Grant rights to the specified accounts. Each account may be a string, an NTAccount object, or a SecurityIdentifier object. [Parameter(Mandatory, Position = 2, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Object[]]$AccountName ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { foreach ($account in $AccountName) { try { $userRights = $Name | Resolve-UserRight if ($pscmdlet.ShouldProcess(('Adding {0} to {1}' -f $account, $userRights.Name -join ', '))) { $lsa.AddAccountRights($account, $userRights.Name) } } catch { Write-Error -ErrorRecord $_ } } } end { CloseLsaPolicy $lsa } } filter Install-GroupManagedServiceAccount { <# .SYNOPSIS Adds a Group Managed Service Account to the local machine. .DESCRIPTION Adds a Group Managed Service Account to the local machine. .EXAMPLE Install-GroupManagedServiceAccount -AccountName domain\name$ #> [CmdletBinding()] param ( # The name of the Group Managed Service Account. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String]$AccountName ) try { [ServiceAccount]::AddServiceAccount($AccountName) } catch { $pscmdlet.ThrowTerminatingError($_) } } filter Reset-SecurityOption { <# .SYNOPSIS Reset the value of a security option to its default. .DESCRIPTION Reset the value of a security option to its default. .EXAMPLE Reset-SecurityOption FIPSAlgorithmPolicy Resets the FIPSAlgorithmPolicy policy to the default value, Disabled. .EXAMPLE Reset-SecurityOption 'Interactive logon: Message text for users attempting to log on' Resets the LegalNoticeText policy to an empty string. .EXAMPLE Reset-SecurityOption ForceKeyProtection Resets the ForceKeyProtection policy to "Not Defined" by removing the associated registry value. #> [CmdletBinding(SupportsShouldProcess)] param ( # The name of the security option to set. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String[]]$Name ) foreach ($securityOptionInfo in $Name | Resolve-SecurityOption | Sort-Object Category, ShortDescription) { $value = $securityOptionInfo.Default if ($value -eq 'Not Defined' -and $securityOptionInfo.Key) { if (Test-Path $securityOptionInfo.Key) { $key = Get-Item -Path $securityOptionInfo.Key if ($key.GetValueNames() -contains $securityOptionInfo.Name) { Remove-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name } } } else { Set-SecurityOption -Name $securityOptionInfo.Name -Value $value } } } filter Resolve-SecurityOption { <# .SYNOPSIS Resolves the name of a security option as shown in the local security policy editor. .DESCRIPTION Resolves the name of a security option as shown in the local security policy editor to either the registry value name, or the name of an implementing class. .EXAMPLE Resolve-SecurityOption "User Account Control: Run all administrators in Admin Approval Mode" Returns information about the security option. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [CmdletBinding()] param ( # The name or description of a user right. Wildcards are supported for description values. [Parameter(Position = 1, ValueFromPipeline, ParameterSetName = 'ByName')] [String]$Name, # Get the policies under a specific category, for example "Network security". [Parameter(Mandatory, ParameterSetName = 'ByCategory')] [ArgumentCompleter( { param ( [String]$CommandName, [String]$ParameterName, [String]$WordToComplete ) [System.Collections.Generic.HashSet[String]](Resolve-SecurityOption).Category -like "$WordToComplete*" } )] [String]$Category ) if ($Name) { if ($Script:securityOptionData.Contains($Name)) { $Script:securityOptionData[$Name] } elseif ($Script:securityOptionLookupHelper.Contains($Name)) { $Script:securityOptionData[$Script:securityOptionLookupHelper[$Name]] } else { $isLikeDescription = $false foreach ($value in $Script:securityOptionLookupHelper.Keys -like $Name) { $isLikeDescription = $true $Script:securityOptionData[$Script:securityOptionLookupHelper[$value]] } if (-not $isLikeDescription) { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new('"{0}" does not resolve to a security option' -f $Name), 'CannotResolveSecurityOption', 'InvalidArgument', $Name ) $pscmdlet.ThrowTerminatingError($errorRecord) } } } elseif ($Category) { $Script:securityOptionData.Values.Where{ $_.Category -like $Category } } else { $Script:securityOptionData.Values } } filter Resolve-UserRight { <# .SYNOPSIS Resolves the name of a user right as shown in the local security policy editor to its constant name. .DESCRIPTION Resolves the name of a user right as shown in the local security policy editor to its constant name. .EXAMPLE Resolve-UserRight "Generate security audits" Returns the value SeAuditPrivilege. .EXAMPLE Resolve-UserRight "*batch*" Returns SeBatchLogonRight and SeDenyBatchLogonRight via the description. .EXAMPLE Resolve-UserRight SeBatchLogonRight Returns the name and description of the user right. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] [CmdletBinding()] param ( # The name or description of a user right. Wildcards are supported for description values. [Parameter(Position = 1, ValueFromPipeline)] [String]$Name ) if ($Name) { if ($Script:userRightData.Contains($Name)) { $Script:userRightData[$Name] } elseif ($Script:userRightLookupHelper.Contains($Name)) { $Script:userRightData[$Script:userRightLookupHelper[$Name]] } else { $isLikeDescription = $false foreach ($value in $Script:userRightLookupHelper.Keys -like $Name) { $isLikeDescription = $true $Script:userRightData[$Script:userRightLookupHelper[$value]] } if (-not $isLikeDescription) { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new('"{0}" does not resolve to a user right' -f $Name), 'UserRightCannotResolve', 'InvalidArgument', $Name ) $pscmdlet.ThrowTerminatingError($errorRecord) } } } else { $Script:userRightData.Values } } function Revoke-UserRight { <# .SYNOPSIS Revokes rights granted to an account. .DESCRIPTION Revokes the rights granted to an account or set of accounts. The All switch may be used to revoke all rights granted to the specified accounts. .EXAMPLE Revoke-UserRight -Name 'Log on as a service' -AccountName 'JonDoe' Revokes the logon as a service right granted to the account named JonDoe. .EXAMPLE Revoke-UserRight -AccountName 'JonDoe' -All Revokes all rights which have been granted to the identity JonDoe. #> [CmdletBinding(DefaultParameterSetName = 'List', SupportsShouldProcess)] param ( # The user right, or rights, to query. [Parameter(Mandatory, Position = 1, ParameterSetName = 'List')] [ValidateScript( { $_ | Resolve-UserRight } )] [Alias('UserRight')] [String[]]$Name, # Grant rights to the specified principals. The principal may be a string, an NTAccount object, or a SecurityIdentifier object. [Parameter(Mandatory, Position = 2)] [Object[]]$AccountName, # Clear all rights granted to the specified accounts. [Parameter(Mandatory, ParameterSetName = 'All')] [Switch]$AllRights ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { foreach ($account in $AccountName) { try { if ($pscmdlet.ParameterSetName -eq 'All' -and $AllRights) { if ($pscmdlet.ShouldProcess(('Removing all rights from {0}' -f $account))) { $lsa.RemoveAllAccountRights($account) } } elseif ($pscmdlet.ParameterSetName -eq 'List') { $userRights = $Name | Resolve-UserRight if ($pscmdlet.ShouldProcess(('Removing {0} from {1}' -f $account, $userRights.Name -join ', '))) { $lsa.RemoveAccountRights($account, $userRights.Name) } } } catch { Write-Error -ErrorRecord $_ } } } end { CloseLsaPolicy $lsa } } filter Set-SecurityOption { <# .SYNOPSIS Set the value of a security option. .DESCRIPTION Set the value of a security option. .PARAMETER Value The value to set. .EXAMPLE Set-SecurityOption EnableLUA Enabled Enables the "User Account Control: Run all administrators in Admin Approval Mode" policy. .EXAMPLE Set-SecurityOption LegalNoticeText '' Sets the value of the LegalNoticeText policy to an empty string. .EXAMPLE #> [CmdletBinding(SupportsShouldProcess)] param ( # The name of the security option to set. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String]$Name, # The value to set. [Parameter(Mandatory, Position = 2, ValueFromPipelineByPropertyName)] [AllowNull()] [AllowEmptyString()] [AllowEmptyCollection()] [Object]$Value ) if ($value -eq 'Not Defined') { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new('Cannot set "Not Defined" for {0}. Please use Reset-SecurityOption' -f $Name), 'CannotUseSetToReset', 'InvalidArgument', $Value ) $pscmdlet.ThrowTerminatingError($errorRecord) } $securityOptionInfo = Resolve-SecurityOption $Name $valueType = $securityOptionInfo.ValueType -as [Type] if ($valueType.BaseType -eq [Enum]) { $parsedValue = 0 -as $valueType if ($valueType::TryParse([String]$Value, $true, [Ref]$parsedValue)) { $Value = $parsedValue } else { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new(('{0} is not a valid value for {1}. Valid values are: {2}' -f $Value, $securityOptionInfo.ValueName, ([Enum]::GetNames($valueType) -join ', ') )), 'InvalidValueForSecurityOption', 'InvalidArgument', $Value ) $pscmdlet.ThrowTerminatingError($errorRecord) } } try { if ($securityOptionInfo.Key) { Write-Debug ('Registry value type: {0}' -f $securityOption.Name) if (Test-Path $securityOptionInfo.Key) { $key = Get-Item -Path $securityOptionInfo.Key } else { $key = New-Item -Path $securityOptionInfo.Key -ItemType Key -Force } if ($key.GetValueNames() -contains $securityOptionInfo.Name) { $currentValue = Get-ItemPropertyValue -Path $key.PSPath -Name $securityOptionInfo.Name $shouldSet = $false if ($value -is [Array] -or $currentValue -is [Array]) { $shouldSet = ($currentValue -join ' ') -ne ($value -join ' ') } elseif ($currentValue -ne $Value) { $shouldSet = $true } if ($shouldSet -and $pscmdlet.ShouldProcess(('Setting policy {0} to {1}' -f $securityOption.Name, $Value))) { Set-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name -Value $Value } } else { $propertyType = switch ($securityOptionInfo.ValueType) { { $valueType.BaseType -eq [Enum] } { $_ = ($_ -as [Type]).GetEnumUnderlyingType().Name } 'Int32' { 'DWord'; break } 'Int64' { 'QWord'; break } 'String' { 'String'; break } 'String[]' { 'MultiString'; break } default { throw 'Invalid or unhandled registry property type' } } if ($pscmdlet.ShouldProcess(('Setting policy {0} to {1} with value type {2}' -f $securityOption.Name, $Value, $propertyType))) { New-ItemProperty -Path $key.PSPath -Name $securityOptionInfo.Name -Value $Value -PropertyType $propertyType } } } else { Write-Debug ('Class-handled value type: {0}' -f $securityOption.Name) $class = NewImplementingType $securityOptionInfo.Class $class.Value = $Value if (-not $class.Test()) { if ($pscmdlet.ShouldProcess(('Setting policy {0} to {1}' -f $securityOption.Name, $Value))) { $class.Set() } } } } catch { $pscmdlet.ThrowTerminatingError($_) } } function Set-UserRight { <# .SYNOPSIS Set the value of a user rights assignment to the specified list of principals. .DESCRIPTION Set the value of a user rights assignment to the specified list of principals, replacing any existing entries. .EXAMPLE Set-UserRight -Name SeShutdownPrivilege -AccountName 'Administrators' Replaces the accounts granted the SeShutdownPrivilege right with Administrators. #> [CmdletBinding(SupportsShouldProcess)] param ( # The user right, or rights, to query. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [ValidateScript( { $_ | Resolve-UserRight } )] [Alias('UserRight')] [String[]]$Name, # The list of identities which should set for the specified policy. [Parameter(Mandatory, Position = 2, ValueFromPipelineByPropertyName)] [Object[]]$AccountName ) begin { try { $lsa = OpenLsaPolicy } catch { $pscmdlet.ThrowTerminatingError($_) } } process { try { # Ensure all identity values are SecurityIdentifier type $requestedIdentities = foreach ($account in $AccountName) { $securityIdentifier = switch ($account.GetType()) { ([String]) { ([NTAccount]$account).Translate([SecurityIdentifier]); break } ([NTAccount]) { $account.Translate([SecurityIdentifier]); break } default { $account } } [PSCustomObject]@{ Value = $account SecurityIdentifier = $securityIdentifier } } foreach ($userRight in $Name | Resolve-UserRight) { # Get the current value and ensure all returned values are SecurityIdentifier type $currentIdentities = foreach ($account in $lsa.EnumerateAccountsWithUserRight($userRight.Name)) { $securityIdentifier = if ($account -is [NTAccount]) { $account.Translate([SecurityIdentifier]) } else { $account } [PSCustomObject]@{ Value = $account SecurityIdentifier = $securityIdentifier } } foreach ($account in Compare-Object @($requestedIdentities) @($currentIdentities) -Property SecurityIdentifier -PassThru) { if ($account.SideIndicator -eq '<=') { if ($pscmdlet.ShouldProcess(('Adding {0} to user right {1}' -f $account.Value, $userRight.Name))) { $lsa.AddAccountRights($account.SecurityIdentifier, $userRight.Name) } } elseif ($account.SideIndicator -eq '=>') { if ($pscmdlet.ShouldProcess(('Removing {0} from user right {1}' -f $account.Value, $userRight.Name))) { $lsa.RemoveAccountRights($account.SecurityIdentifier, [UserRight[]]$userRight.Name) } } } } } catch { Write-Error -ErrorRecord $_ } } end { CloseLsaPolicy $lsa } } filter Test-GroupManagedServiceAccount { <# .SYNOPSIS Test whether or not a Group Managed Service Account is installed in the NetLogon store. .DESCRIPTION Test whether or not a Group Managed Service Account is installed in the NetLogon store. #> [CmdletBinding()] param ( # The name of the Group Managed Service Account. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String]$AccountName ) try { return [Indented.SecurityPolicy.ServiceAccount]::IsServiceAccount($AccountName) } catch { $pscmdlet.ThrowTerminatingError($_) } } filter Uninstall-GroupManagedServiceAccount { <# .SYNOPSIS Uninstalls a Group Managed Service Account from the local machine. .DESCRIPTION Uninstalls a Group Managed Service Account from the local machine. #> [CmdletBinding()] param ( # The name of the Group Managed Service Account. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String]$AccountName ) try { if (Test-GroupManagedServiceAccount $AccountName) { [Indented.SecurityPolicy.ServiceAccount]::RemoveServiceAccount($AccountName) } else { $errorRecord = [ErrorRecord]::new( [ArgumentException]::new('The specified account, {0}, is not installed' -f $AccountName), 'GMSANotInstalled', 'InvalidArgument', $AccountName ) $pscmdlet.ThrowTerminatingError($errorRecord) } } catch { $pscmdlet.ThrowTerminatingError($_) } } function InitializeModule { ImportSecurityOptionData ImportUserRightData $manifest = Join-Path $myinvocation.MyCommand.Module.ModuleBase ('{0}.psd1' -f $myinvocation.MyCommand.Module.Name) $commands = (Import-PowerShellDataFile $manifest).FunctionsToExport Register-ArgumentCompleter -CommandName ($commands -like '*UserRight') -ParameterName Name -ScriptBlock { param ( [String]$CommandName, [String]$ParameterName, [String]$WordToComplete ) [Indented.SecurityPolicy.UserRight].GetEnumValues().Where{ $_ -like "$WordToComplete*" } } Register-ArgumentCompleter -CommandName ($commands -like '*SecurityOption') -ParameterName Name -ScriptBlock { param ( [String]$CommandName, [String]$ParameterName, [String]$WordToComplete ) (Resolve-SecurityOption | Where-Object Name -like "$WordToComplete*").Name } } InitializeModule |