mirror of https://github.com/raandree/NTFSSecurity
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
23 KiB
541 lines
23 KiB
<#
|
|
Tests how the cmdlets of the module built in NTFSSecurity\bin\Release handle the Backup, Restore, Take Ownership,
|
|
and Security privileges. These tests need an access token that holds the privileges, so they skip without them
|
|
and run in CI, whose runners are elevated. They change only the privileges of the test process and restore the
|
|
module setting EnablePrivileges.
|
|
#>
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
|
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
|
|
)]
|
|
param ()
|
|
|
|
BeforeDiscovery {
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
$missingPrivileges = @('SeBackupPrivilege', 'SeRestorePrivilege', 'SeTakeOwnershipPrivilege', 'SeSecurityPrivilege') |
|
|
Where-Object -FilterScript { -not (Test-PrivilegeHeld -Name $_) }
|
|
$holdsPrivileges = -not $missingPrivileges
|
|
}
|
|
|
|
BeforeAll {
|
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
|
|
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
|
|
Import-Module -Name $modulePath -Force -ErrorAction Stop
|
|
$sandbox = New-TestSandbox -Name 'Privileges'
|
|
Push-Location -LiteralPath $sandbox
|
|
|
|
$privateData = (Get-Module -Name NTFSSecurity).PrivateData
|
|
$enablePrivileges = $privateData['EnablePrivileges']
|
|
|
|
function Get-BackupPrivilegeState {
|
|
(Get-Privileges | Where-Object -Property Privilege -EQ -Value 'Backup').PrivilegeState
|
|
}
|
|
|
|
function Get-EnabledFileSystemPrivilege {
|
|
# The names of the privileges that the cmdlets enable, as far as they are enabled now
|
|
@(Get-Privileges | Where-Object -FilterScript {
|
|
$_.Privilege -in 'TakeOwnership', 'Restore', 'Backup', 'Security' -and $_.PrivilegeState -eq 'Enabled'
|
|
} | ForEach-Object -Process { $_.Privilege.ToString() })
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
Pop-Location
|
|
Remove-TestSandbox -Sandbox $sandbox
|
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Describe 'Disable-Privileges' {
|
|
Context 'When the module setting EnablePrivileges is $false' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
# Before 5.0.0, the cmdlet warned that it could not disable the privileges and left them enabled.
|
|
It 'Should disable the privileges that Enable-Privileges enabled' -Skip:(-not $holdsPrivileges) {
|
|
Enable-Privileges
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
|
|
Disable-Privileges -WarningVariable privilegeWarnings -WarningAction SilentlyContinue
|
|
|
|
$privilegeWarnings | Should -BeNullOrEmpty
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
|
|
# Before 5.0.0, the verbose message said that the privileges were now enabled.
|
|
It 'Should say in the verbose message that the privileges are disabled' -Skip:(-not $holdsPrivileges) {
|
|
Enable-Privileges
|
|
|
|
$messages = Disable-Privileges -Verbose -WarningAction SilentlyContinue 4>&1
|
|
|
|
$messages.Message | Should -Contain "The privileges 'TakeOwnership', 'Restore' and 'Backup' are now disabled."
|
|
}
|
|
}
|
|
|
|
Context 'When the access token holds only some of the privileges' {
|
|
# Before 5.0.0-rc4, the cmdlet also tried to disable the privileges that the access token doesn't hold, and
|
|
# warned for each one that it couldn't disable it. A removed privilege can't be added back, so the test removes
|
|
# them in a child process.
|
|
It 'Should not warn about the privileges that the access token does not hold' -Skip:(-not $holdsPrivileges) {
|
|
$script = Join-Path -Path $sandbox -ChildPath 'Disable-PartialPrivileges.ps1'
|
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $script
|
|
Set-Content -LiteralPath $script -Value @'
|
|
param ($ModulePath)
|
|
Import-Module -Name $ModulePath -ErrorAction Stop
|
|
$process = [System.Diagnostics.Process]::GetCurrentProcess()
|
|
[ProcessPrivileges.ProcessExtensions]::RemovePrivilege($process, [ProcessPrivileges.Privilege]::TakeOwnership) | Out-Null
|
|
[ProcessPrivileges.ProcessExtensions]::RemovePrivilege($process, [ProcessPrivileges.Privilege]::Security) | Out-Null
|
|
Enable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
Disable-Privileges -WarningVariable privilegeWarnings -WarningAction SilentlyContinue
|
|
'WARNINGS:{0}' -f @($privilegeWarnings).Count
|
|
'@
|
|
|
|
$output = & (Get-Process -Id $PID).Path -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script -ModulePath ([IO.Path]::GetFullPath($modulePath))
|
|
|
|
$output | Should -Contain 'WARNINGS:0'
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Inheritance cmdlets' {
|
|
Context 'When the module setting EnablePrivileges is $false' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Inheritance'
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
# Before 5.0.0, the inheritance cmdlets enabled the privileges anyway and left them enabled.
|
|
It '<_> should leave the privileges disabled' -Skip:(-not $holdsPrivileges) -ForEach @(
|
|
'Get-NTFSInheritance', 'Set-NTFSInheritance', 'Enable-NTFSAccessInheritance',
|
|
'Disable-NTFSAccessInheritance', 'Enable-NTFSAuditInheritance', 'Disable-NTFSAuditInheritance'
|
|
) {
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
|
|
& $_ -Path $file -ErrorAction SilentlyContinue | Out-Null
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Privileges when the pipeline stops early' {
|
|
BeforeAll {
|
|
# The cmdlets enable the privileges only with this setting; without it, these tests would prove nothing.
|
|
$privateData['EnablePrivileges'] = $true
|
|
$files = 1..3 | ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name "Stopped$_" }
|
|
$missing = Join-Path -Path $sandbox -ChildPath 'StoppedMissing.txt'
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
BeforeEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
AfterEach {
|
|
# A failing test must not leave the privileges enabled for the tests that follow.
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
# Before 5.0.0-rc6, a cmdlet disabled the privileges that it had enabled only in EndProcessing, which PowerShell
|
|
# skips when a later command or a terminating error stops the pipeline. The Backup, Restore, Take Ownership, and
|
|
# Security privileges then stayed enabled in the session.
|
|
It 'Should disable the privileges after Select-Object -First stops the pipeline' -Skip:(-not $holdsPrivileges) {
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
|
|
$stateWhileRunning = Get-NTFSOwner -Path $files | ForEach-Object -Process { Get-BackupPrivilegeState } |
|
|
Select-Object -First 1
|
|
|
|
$stateWhileRunning | Should -Be 'Enabled'
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
|
|
It 'Should disable the privileges after a terminating error' -Skip:(-not $holdsPrivileges) {
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
$statesWhileRunning = New-Object -TypeName 'System.Collections.Generic.List[string]'
|
|
|
|
{
|
|
Get-NTFSAccess -Path $files[0], $missing -ErrorAction Stop |
|
|
ForEach-Object -Process { $statesWhileRunning.Add((Get-BackupPrivilegeState)) }
|
|
} | Should -Throw
|
|
|
|
$statesWhileRunning | Should -Not -BeNullOrEmpty
|
|
$statesWhileRunning | Should -Not -Contain 'Disabled'
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
|
|
It 'Enable-Privileges should keep the privileges enabled also when the pipeline stops early' -Skip:(-not $holdsPrivileges) {
|
|
Enable-Privileges -PassThru | Select-Object -First 1 | Out-Null
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
}
|
|
}
|
|
|
|
Describe 'Privileges that another command in the pipeline changes' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $true
|
|
$files = 1..3 | ForEach-Object -Process { New-TestSandboxItem -Sandbox $sandbox -Name "Changed$_" }
|
|
|
|
function Disable-TakeOwnershipOnce {
|
|
# Passes the objects on and disables the Take Ownership privilege when the first one passes, as another
|
|
# command in the pipeline can. Records the state of the Backup privilege at that moment.
|
|
param (
|
|
[Parameter(ValueFromPipeline)]
|
|
[object]
|
|
$InputObject,
|
|
|
|
[Parameter(Mandatory)]
|
|
[AllowEmptyCollection()]
|
|
[System.Collections.Generic.List[string]]
|
|
$BackupState
|
|
)
|
|
|
|
begin {
|
|
$first = $true
|
|
}
|
|
|
|
process {
|
|
if ($first) {
|
|
$BackupState.Add((Get-BackupPrivilegeState))
|
|
$null = [ProcessPrivileges.ProcessExtensions]::DisablePrivilege(
|
|
[System.Diagnostics.Process]::GetCurrentProcess(), [ProcessPrivileges.Privilege]::TakeOwnership
|
|
)
|
|
$first = $false
|
|
}
|
|
|
|
$InputObject
|
|
}
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
BeforeEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
AfterEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
# Before 5.0.0-rc6, a cmdlet decided which privileges to disable on the states that it had read when it enabled
|
|
# them. A privilege that another command had disabled since then stopped it with "Priviledge already disabled",
|
|
# and the privileges after that one in its list stayed enabled.
|
|
It 'Should disable the other privileges when another command disabled one of them' -Skip:(-not $holdsPrivileges) {
|
|
$backupState = New-Object -TypeName 'System.Collections.Generic.List[string]'
|
|
|
|
{ Get-NTFSOwner -Path $files | Disable-TakeOwnershipOnce -BackupState $backupState | Out-Null } | Should -Not -Throw
|
|
|
|
$backupState | Should -Be 'Enabled'
|
|
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Should disable the other privileges when another command disabled one of them and the pipeline stops early' -Skip:(-not $holdsPrivileges) {
|
|
$backupState = New-Object -TypeName 'System.Collections.Generic.List[string]'
|
|
|
|
Get-NTFSOwner -Path $files | Disable-TakeOwnershipOnce -BackupState $backupState | Select-Object -First 1 | Out-Null
|
|
|
|
$backupState | Should -Be 'Enabled'
|
|
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
|
|
}
|
|
|
|
It 'Should not fail when Disable-Privileges runs inside the pipeline' -Skip:(-not $holdsPrivileges) {
|
|
{
|
|
Get-NTFSOwner -Path $files |
|
|
ForEach-Object -Process { Disable-Privileges -WarningAction SilentlyContinue; $_ } |
|
|
Out-Null
|
|
} | Should -Not -Throw
|
|
|
|
Get-EnabledFileSystemPrivilege | Should -BeNullOrEmpty
|
|
}
|
|
}
|
|
|
|
# The library class of the module that the cmdlets leave unused; the tests change only the privileges of the test process.
|
|
Describe 'The PrivilegeEnabler class' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
$backup = [ProcessPrivileges.Privilege]::Backup
|
|
$changeNotify = [ProcessPrivileges.Privilege]::ChangeNotify
|
|
$currentProcess = [System.Diagnostics.Process]::GetCurrentProcess()
|
|
|
|
# The enabler goes out of scope in the function, so that nothing but the caller's handle refers to what it owns.
|
|
function New-AbandonedHandle {
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
|
|
'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Test helper that only creates an object.'
|
|
)]
|
|
param ($Process)
|
|
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $Process
|
|
$field = [ProcessPrivileges.PrivilegeEnabler].GetField('accessTokenHandle', [System.Reflection.BindingFlags] 'NonPublic, Instance')
|
|
$field.GetValue($enabler)
|
|
}
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
BeforeEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
AfterEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
It 'Should enable a disabled privilege until it is disposed' -Skip:(-not $holdsPrivileges) {
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess, $backup
|
|
try {
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
}
|
|
finally {
|
|
$enabler.Dispose()
|
|
}
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
$enabler.Dispose()
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
|
|
It 'Should report a privilege that it modified once and leave it to the instance that enabled it' -Skip:(-not $holdsPrivileges) {
|
|
$first = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
|
|
$second = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
|
|
try {
|
|
$first.EnablePrivilege($backup) | Should -Be 'PrivilegeModified'
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
$first.EnablePrivilege($backup) | Should -Be 'None'
|
|
$second.EnablePrivilege($backup) | Should -Be 'None'
|
|
$second.Dispose()
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
}
|
|
finally {
|
|
$first.Dispose()
|
|
$second.Dispose()
|
|
}
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
|
|
It 'Should not disable a privilege that was enabled before' -Skip:(-not $holdsPrivileges) {
|
|
$null = [ProcessPrivileges.ProcessExtensions]::EnablePrivilege($currentProcess, $backup)
|
|
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess, $backup
|
|
try {
|
|
$enabler.EnablePrivilege($backup) | Should -Be 'None'
|
|
}
|
|
finally {
|
|
$enabler.Dispose()
|
|
}
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
}
|
|
|
|
It 'Should enable a privilege through an access token handle that the caller owns' -Skip:(-not $holdsPrivileges) {
|
|
$rights = [ProcessPrivileges.TokenAccessRights]::AdjustPrivileges -bor [ProcessPrivileges.TokenAccessRights]::Query
|
|
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $rights)
|
|
$enabler = $null
|
|
try {
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $handle, $backup
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
$enabler.Dispose()
|
|
$enabler = $null
|
|
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
$handle.IsClosed | Should -BeFalse
|
|
}
|
|
finally {
|
|
# The enabler first: a handle that is closed under an enabler that still owns a privilege fails when the
|
|
# enabler disables the privilege.
|
|
if ($enabler) {
|
|
$enabler.Dispose()
|
|
}
|
|
$handle.Dispose()
|
|
}
|
|
|
|
$handle.IsClosed | Should -BeTrue
|
|
}
|
|
|
|
# The finalizer closes the token handle that an abandoned enabler opened and drops its registration, so that the next
|
|
# enabler for the process opens a handle of its own instead of taking a closed one. The handle is private, so the test
|
|
# reads it by reflection. An enabler that enabled a privilege stays referenced by a static list until it is disposed,
|
|
# so it is never finalized and its privilege stays enabled; only an enabler without a privilege can be abandoned.
|
|
It 'Should close the token handle of an enabler that was never disposed when it is finalized' {
|
|
$handle = New-AbandonedHandle -Process $currentProcess
|
|
$handle.IsClosed | Should -BeFalse
|
|
|
|
for ($attempt = 0; $attempt -lt 10 -and -not $handle.IsClosed; $attempt++) {
|
|
[GC]::Collect()
|
|
[GC]::WaitForPendingFinalizers()
|
|
}
|
|
|
|
$handle.IsClosed | Should -BeTrue
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
|
|
try {
|
|
$enabler.EnablePrivilege($changeNotify) | Should -Be 'None'
|
|
}
|
|
finally {
|
|
$enabler.Dispose()
|
|
}
|
|
}
|
|
|
|
# The access tokens of administrators don't hold the privilege to create a token, and those of basic users don't hold
|
|
# most of the others.
|
|
It 'Should leave a privilege that the access token does not hold alone' {
|
|
$removed = [ProcessPrivileges.Privilege]::CreateToken
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($currentProcess, $removed) | Should -Be 'Removed'
|
|
|
|
$enabler = New-Object -TypeName 'ProcessPrivileges.PrivilegeEnabler' -ArgumentList $currentProcess
|
|
try {
|
|
$enabler.EnablePrivilege($removed) | Should -Be 'None'
|
|
}
|
|
finally {
|
|
$enabler.Dispose()
|
|
}
|
|
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($currentProcess, $removed) | Should -Be 'Removed'
|
|
}
|
|
|
|
# The enabled flag decides first, then the removed flag; the attributes are not a flags enumeration in .NET.
|
|
It 'Should derive the state <Expected> from the attribute value <Value>' -ForEach @(
|
|
@{ Value = 0; Expected = 'Disabled' }
|
|
@{ Value = 1; Expected = 'Disabled' }
|
|
@{ Value = 2; Expected = 'Enabled' }
|
|
@{ Value = 3; Expected = 'Enabled' }
|
|
@{ Value = 4; Expected = 'Removed' }
|
|
@{ Value = 6; Expected = 'Enabled' }
|
|
@{ Value = -2147483648; Expected = 'Disabled' }
|
|
) {
|
|
$attributes = [Enum]::ToObject([ProcessPrivileges.PrivilegeAttributes], $Value)
|
|
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($attributes) | Should -Be $Expected
|
|
}
|
|
}
|
|
|
|
# Every access token holds the privilege to bypass traverse checking, enabled. The tests use it because they need no other
|
|
# privilege and change nothing: a handle that lacks a right fails before it adjusts anything.
|
|
Describe 'The access token handle of a process' {
|
|
BeforeAll {
|
|
$currentProcess = [System.Diagnostics.Process]::GetCurrentProcess()
|
|
$changeNotify = [ProcessPrivileges.Privilege]::ChangeNotify
|
|
$tokenRights = [ProcessPrivileges.TokenAccessRights]
|
|
}
|
|
|
|
It 'Should open a handle with all access rights when the caller names none and close it on dispose' {
|
|
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess)
|
|
try {
|
|
$handle.IsInvalid | Should -BeFalse
|
|
@([ProcessPrivileges.ProcessExtensions]::GetPrivileges($handle)) | Should -Not -BeNullOrEmpty
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify) | Should -Be 'Enabled'
|
|
}
|
|
finally {
|
|
$handle.Dispose()
|
|
}
|
|
|
|
$handle.IsClosed | Should -BeTrue
|
|
}
|
|
|
|
It 'Should refuse to enable a privilege through a handle that may only query' {
|
|
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $tokenRights::Query)
|
|
try {
|
|
$failure = { [ProcessPrivileges.ProcessExtensions]::EnablePrivilege($handle, $changeNotify) } | Should -Throw -PassThru
|
|
|
|
$failure.Exception.InnerException | Should -BeOfType [System.ComponentModel.Win32Exception]
|
|
$failure.Exception.InnerException.NativeErrorCode | Should -Be 5
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify) | Should -Be 'Enabled'
|
|
}
|
|
finally {
|
|
$handle.Dispose()
|
|
}
|
|
}
|
|
|
|
It 'Should refuse to <Operation> through a handle that may only adjust privileges' -ForEach @(
|
|
@{ Operation = 'list the privileges' }
|
|
@{ Operation = 'read the state of a privilege' }
|
|
) {
|
|
$handle = [ProcessPrivileges.ProcessExtensions]::GetAccessTokenHandle($currentProcess, $tokenRights::AdjustPrivileges)
|
|
try {
|
|
$failure = {
|
|
if ($Operation -eq 'list the privileges') {
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivileges($handle)
|
|
}
|
|
else {
|
|
[ProcessPrivileges.ProcessExtensions]::GetPrivilegeState($handle, $changeNotify)
|
|
}
|
|
} | Should -Throw -PassThru
|
|
|
|
$failure.Exception.InnerException | Should -BeOfType [System.ComponentModel.Win32Exception]
|
|
$failure.Exception.InnerException.NativeErrorCode | Should -Be 5
|
|
}
|
|
finally {
|
|
$handle.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'The PrivilegeControl class' {
|
|
BeforeAll {
|
|
$privateData['EnablePrivileges'] = $false
|
|
$control = New-Object -TypeName 'Security2.PrivilegeControl'
|
|
$backup = [ProcessPrivileges.Privilege]::Backup
|
|
}
|
|
|
|
AfterAll {
|
|
$privateData['EnablePrivileges'] = $enablePrivileges
|
|
}
|
|
|
|
BeforeEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
AfterEach {
|
|
Disable-Privileges -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
|
|
}
|
|
|
|
It 'Should refuse to <Operation> a privilege that the access token does not hold' -ForEach @(
|
|
@{ Operation = 'enable' }
|
|
@{ Operation = 'disable' }
|
|
) {
|
|
$failure = {
|
|
if ($Operation -eq 'enable') {
|
|
$control.EnablePrivilege([ProcessPrivileges.Privilege]::CreateToken)
|
|
}
|
|
else {
|
|
$control.DisablePrivilege([ProcessPrivileges.Privilege]::CreateToken)
|
|
}
|
|
} | Should -Throw -PassThru
|
|
|
|
$failure.Exception.InnerException | Should -BeOfType [System.Security.AccessControl.PrivilegeNotHeldException]
|
|
$failure.Exception.InnerException.PrivilegeName | Should -BeExactly 'CreateToken'
|
|
}
|
|
|
|
It 'Should enable and disable a held privilege and refuse to repeat either' -Skip:(-not $holdsPrivileges) {
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
$failure = { $control.DisablePrivilege($backup) } | Should -Throw -PassThru
|
|
$failure.Exception.InnerException | Should -BeOfType [Security2.AdjustPriviledgeException]
|
|
$failure.Exception.InnerException.Message | Should -BeExactly 'Priviledge already disabled'
|
|
|
|
$control.EnablePrivilege($backup) | Should -Be 'PrivilegeModified'
|
|
Get-BackupPrivilegeState | Should -Be 'Enabled'
|
|
$failure = { $control.EnablePrivilege($backup) } | Should -Throw -PassThru
|
|
$failure.Exception.InnerException | Should -BeOfType [Security2.AdjustPriviledgeException]
|
|
$failure.Exception.InnerException.Message | Should -BeExactly 'Priviledge already enabled'
|
|
|
|
$control.DisablePrivilege($backup) | Should -Be 'PrivilegeModified'
|
|
Get-BackupPrivilegeState | Should -Be 'Disabled'
|
|
}
|
|
}
|
|
|