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 } [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 } } [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 } } } 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 = GetIadsLocalUser -Sid $this.GetSid() $this.Value = [Enabled][Int]$localUser.Enabled return $this } [Void] Set() { if ([Boolean][Int]$this.Value) { EnableIadsLocalUser -Sid $this.GetSid() } else { DisableIadsLocalUser -Sid $this.GetSid() } } [Boolean] Test() { $localUser = GetIadsLocalUser -Sid $this.GetSid() return $localUser.Enabled -eq ([Boolean][Int]$this.Value) } } class AccountStatusAdministrator : AccountStatus { [WellKnownSidType]$SidType = 'AccountAdministratorSid' } class AccountStatusGuest : AccountStatus { [WellKnownSidType]$SidType = 'AccountGuestSid' } class RenameAccount : AccountBase { [String]$Value [RenameAccount] Get() { $this.Value = (GetIadsLocalUser -Sid $this.GetSid()).Name return $this } [Void] Set() { RenameIadsLocalUser -Sid $this.GetSid() -NewName $this.Value } [Boolean] Test() { $localUser = GetIadsLocalUser -Sid $this.GetSid() return $localUser.Name -eq $this.Value } } class RenameAccountAdministrator : RenameAccount { [WellKnownSidType]$SidType = 'AccountAdministratorSid' } class RenameAccountGuest : RenameAccount { [WellKnownSidType]$SidType = 'AccountGuestSid' } function CloseLsaPolicy { <# .SYNOPSIS Close the LSA policy handle if it is open. .DESCRIPTION Close the LSA policy handle if it is open. #> [CmdletBinding()] param ( [AllowNull()] [Object]$lsa ) if ($lsa) { $lsa.Dispose() } } function DisableIadsLocalUser { <# .SYNOPSIS ADSI based alternative to Disable-LocalUser. .DESCRIPTION This simple ADSI-based version of Disable-LocalUser allows use the AccountStatus security option on PowerShell Core. #> [CmdletBinding()] param ( # The SID of a user. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [SecurityIdentifier]$Sid ) process { $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser $userFlags = $iadsUser.Get('userFlags') if (($userFlags -band 2) -eq 0) { $iadsUser.Put( 'userFlags', $userFlags -bor 2 ) $iadsUser.SetInfo() } } } function EnableIadsLocalUser { <# .SYNOPSIS ADSI based alternative to Enable-LocalUser. .DESCRIPTION This simple ADSI-based version of Enable-LocalUser allows use the AccountStatus security option on PowerShell Core. #> [CmdletBinding()] param ( # The SID of a user. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [SecurityIdentifier]$Sid ) process { $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser $userFlags = $iadsUser.Get('userFlags') if (($userFlags -band 2) -eq 2) { $iadsUser.Put( 'userFlags', $userFlags -bxor 2 ) $iadsUser.SetInfo() } } } function GetIadsLocalUser { <# .SYNOPSIS ADSI based alternative to Get-LocalUser. .DESCRIPTION This simple ADSI-based version of Get-LocalUser allows use the AccountStatus and RenameAccount security options on PowerShell Core. #> [CmdletBinding()] param ( # The exact name of a user. [String]$Name, # The SID of a user. [SecurityIdentifier]$Sid, # Return the IAdsUser object as-is. [Switch]$AsIadsUser ) $iadsComputer = [ADSI]('WinNT://{0}' -f $env:COMPUTERNAME) $null = $iadsComputer.Children.SchemaFilter.Add('user') if ($Name) { $iadsUserCollection = $iadsComputer.Children.Find( $Name, 'user' ) } else { $iadsUserCollection = $iadsComputer.Children } foreach ($iadsUser in $iadsUserCollection) { $iadsUserSid = [SecurityIdentifier]::new([Byte[]]$iadsUser.Get('objectSID'), 0) if (-not $Sid -or $iadsUserSid -eq $Sid) { if ($AsIadsUser) { $iadsUser } else { [PSCustomObject]@{ Name = $iadsUser.Get('Name') Enabled = -not ($iadsUser.Get('userFlags') -band 2) SID = $iadsUserSid } } } } } function GetMachineSid { <# .SYNOPSIS Get the SID of the current machine. .DESCRIPTION Get the SID of the current machine. The current machine SID should not be confused with a SID used by Active Directory. #> [CmdletBinding()] [OutputType([System.Security.Principal.SecurityIdentifier])] param ( ) [Indented.SecurityPolicy.Account]::LookupAccountName($env:COMPUTERNAME, $env:COMPUTERNAME) } function GetSecurityOptionData { <# .SYNOPSIS Get option data for the named security option. .DESCRIPTION Get option data for the named security option. #> [CmdletBinding()] param ( # The name of the security option. [Parameter(Mandatory)] [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 { <# .SYNOPSIS Import and merge localized security option data. .DESCRIPTION Import and merge localized security option data. #> [CmdletBinding()] param ( ) try { $params = @{ FileName = 'securityOptions' BindingVariable = 'localizedSecurityOptions' BaseDirectory = $myinvocation.MyCommand.Module.ModuleBase ErrorAction = 'Stop' } Import-LocalizedData @params } catch { Import-LocalizedData @params -UICulture en } $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 { <# .SYNOPSIS Import and merge localized user rights data. .DESCRIPTION Import and merge localized user rights data. #> [CmdletBinding()] param ( ) try { $params = @{ FileName = 'userRights' BindingVariable = 'localizedUserRights' BaseDirectory = $myinvocation.MyCommand.Module.ModuleBase ErrorAction = 'Stop' } Import-LocalizedData @params } catch { Import-LocalizedData @params -UICulture en } $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 { <# .SYNOPSIS A short helper to create a named type. .DESCRIPTION A short helper to create a named type. Persent to help mocking. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Name ) ($Name -as [Type])::new() } function OpenLsaPolicy { <# .SYNOPSIS Open the LSA policy handle. .DESCRIPTION Open the LSA policy handle. #> [CmdletBinding()] [OutputType([Indented.SecurityPolicy.Lsa])] param ( ) 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 RenameIadsLocalUser { <# .SYNOPSIS ADSI based alternative to Rename-LocalUser. .DESCRIPTION This simple ADSI-based version of Rename-LocalUser allows use the RenameAccount security option on PowerShell Core. #> [CmdletBinding()] param ( # The SID of a user. [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)] [SecurityIdentifier]$Sid, # The new name of the user. [Parameter(Mandatory)] [String]$NewName ) process { $iadsUser = GetIadsLocalUser -Sid $Sid -AsIadsUser if ($iadsUser.Get('Name') -ne $NewName) { $iadsUser.Rename($NewName) } } } 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 } } function 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 ) process { 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 } } function 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 ) process { try { [ServiceAccount]::AddServiceAccount($AccountName) } catch { $pscmdlet.ThrowTerminatingError($_) } } } function 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 ) process { 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 } } } } function 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 ) process { 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 } } } function 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 ) process { 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 } } function 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 ) process { 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 } } function 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()] [OutputType([Boolean])] param ( # The name of the Group Managed Service Account. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ValueFromPipelineByPropertyName)] [String]$AccountName ) process { try { return [Indented.SecurityPolicy.ServiceAccount]::IsServiceAccount($AccountName) } catch { $pscmdlet.ThrowTerminatingError($_) } } } function 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 ) process { 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 |