mirror of https://github.com/raandree/NTFSSecurity
committed by
GitHub
64 changed files with 3727 additions and 275 deletions
@ -0,0 +1,215 @@ |
|||||
|
<# |
||||
|
.SYNOPSIS |
||||
|
Runs the Pester tests as a basic user, without the privileges and the Administrators group of the current account. |
||||
|
|
||||
|
.DESCRIPTION |
||||
|
Some tests need a session without the Security, Restore, or Create Symbolic Link privilege, and skip in an elevated |
||||
|
session such as that of a GitHub runner. This script derives a token of the SAFER level Normal User from the token |
||||
|
of the current process, as runas /trustlevel:0x20000 does: the Administrators group is deny-only, and only the |
||||
|
privilege to bypass traverse checking stays. It starts Invoke-Tests.ps1 in the same PowerShell edition with that |
||||
|
token, waits for it, prints its output, and fails if the tests failed. The job summary of GitHub Actions gets the |
||||
|
results through a file of this account, which the restricted token can write. |
||||
|
|
||||
|
.PARAMETER ResultPath |
||||
|
Specifies the path of the result file in the NUnit format. |
||||
|
|
||||
|
.PARAMETER Title |
||||
|
Specifies the heading of the test results in the job summary. It can't contain a double quote, a percent sign, or a |
||||
|
line break, which would change the command line of cmd.exe. |
||||
|
|
||||
|
.EXAMPLE |
||||
|
.\.github\scripts\Invoke-TestsAsBasicUser.ps1 -ResultPath TestResults\WindowsPowerShell-BasicUser.xml -Title 'Windows PowerShell 5.1 as a basic user' |
||||
|
|
||||
|
Runs the tests in Windows PowerShell 5.1 as a basic user. |
||||
|
#> |
||||
|
[CmdletBinding()] |
||||
|
param ( |
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
[string] |
||||
|
$ResultPath, |
||||
|
|
||||
|
[Parameter(Mandatory)] |
||||
|
[ValidateNotNullOrEmpty()] |
||||
|
# cmd.exe gets the title in a quoted argument, expands environment variables also inside quotes, and ends the |
||||
|
# command at a line break. \z, unlike $, doesn't match before a final line feed. |
||||
|
[ValidatePattern('\A[^"%\r\n]+\z')] |
||||
|
[string] |
||||
|
$Title |
||||
|
) |
||||
|
|
||||
|
$ErrorActionPreference = 'Stop' |
||||
|
|
||||
|
Add-Type -TypeDefinition @' |
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
public static class NTFSSecurityBasicUserProcess |
||||
|
{ |
||||
|
private const uint SaferScopeIdUser = 2; |
||||
|
private const uint SaferLevelIdNormalUser = 0x20000; |
||||
|
private const uint SaferLevelOpen = 1; |
||||
|
private const uint CreateNoWindow = 0x08000000; |
||||
|
private const uint Infinite = 0xFFFFFFFF; |
||||
|
private const uint WaitObject0 = 0; |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] |
||||
|
private struct StartupInfo |
||||
|
{ |
||||
|
public int cb; |
||||
|
public string lpReserved; |
||||
|
public string lpDesktop; |
||||
|
public string lpTitle; |
||||
|
public int dwX; |
||||
|
public int dwY; |
||||
|
public int dwXSize; |
||||
|
public int dwYSize; |
||||
|
public int dwXCountChars; |
||||
|
public int dwYCountChars; |
||||
|
public int dwFillAttribute; |
||||
|
public int dwFlags; |
||||
|
public short wShowWindow; |
||||
|
public short cbReserved2; |
||||
|
public IntPtr lpReserved2; |
||||
|
public IntPtr hStdInput; |
||||
|
public IntPtr hStdOutput; |
||||
|
public IntPtr hStdError; |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
private struct ProcessInformation |
||||
|
{ |
||||
|
public IntPtr hProcess; |
||||
|
public IntPtr hThread; |
||||
|
public int dwProcessId; |
||||
|
public int dwThreadId; |
||||
|
} |
||||
|
|
||||
|
[DllImport("advapi32.dll", SetLastError = true)] |
||||
|
private static extern bool SaferCreateLevel(uint scopeId, uint levelId, uint openFlags, out IntPtr levelHandle, IntPtr reserved); |
||||
|
|
||||
|
[DllImport("advapi32.dll", SetLastError = true)] |
||||
|
private static extern bool SaferComputeTokenFromLevel(IntPtr levelHandle, IntPtr inAccessToken, out IntPtr outAccessToken, uint flags, IntPtr reserved); |
||||
|
|
||||
|
[DllImport("advapi32.dll", SetLastError = true)] |
||||
|
private static extern bool SaferCloseLevel(IntPtr levelHandle); |
||||
|
|
||||
|
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] |
||||
|
private static extern bool CreateProcessAsUser(IntPtr token, string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInfo startupInfo, out ProcessInformation processInformation); |
||||
|
|
||||
|
[DllImport("kernel32.dll", SetLastError = true)] |
||||
|
private static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds); |
||||
|
|
||||
|
[DllImport("kernel32.dll", SetLastError = true)] |
||||
|
private static extern bool GetExitCodeProcess(IntPtr process, out uint exitCode); |
||||
|
|
||||
|
[DllImport("kernel32.dll", SetLastError = true)] |
||||
|
private static extern bool CloseHandle(IntPtr handle); |
||||
|
|
||||
|
// Starts the command line with a token of the SAFER level Normal User, waits for it, and returns its exit code. |
||||
|
public static int Run(string applicationName, string commandLine, string currentDirectory) |
||||
|
{ |
||||
|
IntPtr level; |
||||
|
if (!SaferCreateLevel(SaferScopeIdUser, SaferLevelIdNormalUser, SaferLevelOpen, out level, IntPtr.Zero)) |
||||
|
{ |
||||
|
throw new Win32Exception(Marshal.GetLastWin32Error()); |
||||
|
} |
||||
|
|
||||
|
IntPtr token = IntPtr.Zero; |
||||
|
try |
||||
|
{ |
||||
|
if (!SaferComputeTokenFromLevel(level, IntPtr.Zero, out token, 0, IntPtr.Zero)) |
||||
|
{ |
||||
|
throw new Win32Exception(Marshal.GetLastWin32Error()); |
||||
|
} |
||||
|
|
||||
|
var startupInfo = new StartupInfo(); |
||||
|
startupInfo.cb = Marshal.SizeOf(typeof(StartupInfo)); |
||||
|
ProcessInformation processInformation; |
||||
|
if (!CreateProcessAsUser(token, applicationName, commandLine, IntPtr.Zero, IntPtr.Zero, false, CreateNoWindow, IntPtr.Zero, currentDirectory, ref startupInfo, out processInformation)) |
||||
|
{ |
||||
|
throw new Win32Exception(Marshal.GetLastWin32Error()); |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (WaitForSingleObject(processInformation.hProcess, Infinite) != WaitObject0) |
||||
|
{ |
||||
|
throw new Win32Exception(Marshal.GetLastWin32Error()); |
||||
|
} |
||||
|
|
||||
|
uint exitCode; |
||||
|
if (!GetExitCodeProcess(processInformation.hProcess, out exitCode)) |
||||
|
{ |
||||
|
throw new Win32Exception(Marshal.GetLastWin32Error()); |
||||
|
} |
||||
|
|
||||
|
return (int)exitCode; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
CloseHandle(processInformation.hThread); |
||||
|
CloseHandle(processInformation.hProcess); |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (token != IntPtr.Zero) |
||||
|
{ |
||||
|
CloseHandle(token); |
||||
|
} |
||||
|
|
||||
|
SaferCloseLevel(level); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
'@ |
||||
|
|
||||
|
$repositoryPath = (Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath '..\..')).ProviderPath |
||||
|
$resultFullPath = [IO.Path]::GetFullPath((Join-Path -Path $repositoryPath -ChildPath $ResultPath)) |
||||
|
$resultFolder = Split-Path -Path $resultFullPath -Parent |
||||
|
if (-not (Test-Path -LiteralPath $resultFolder)) { |
||||
|
New-Item -ItemType Directory -Path $resultFolder | Out-Null |
||||
|
} |
||||
|
|
||||
|
$runFolder = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath ('NTFSSecurity.BasicUser-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8)) |
||||
|
New-Item -ItemType Directory -Path $runFolder | Out-Null |
||||
|
$logPath = Join-Path -Path $runFolder -ChildPath 'Output.log' |
||||
|
$summaryPath = Join-Path -Path $runFolder -ChildPath 'Summary.md' |
||||
|
# In the temp folder of the account, which the restricted token can write, unlike maybe the folder of the repository |
||||
|
$runResultPath = Join-Path -Path $runFolder -ChildPath 'Result.xml' |
||||
|
|
||||
|
$executable = (Get-Process -Id $PID).Path |
||||
|
$testScript = Join-Path -Path $PSScriptRoot -ChildPath 'Invoke-Tests.ps1' |
||||
|
# cmd redirects the output of the tests, which have no console of their own. |
||||
|
$commandLine = 'cmd.exe /d /s /c ""{0}" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "{1}" -ResultPath "{2}" -Title "{3}" > "{4}" 2>&1"' -f |
||||
|
$executable, $testScript, $runResultPath, $Title, $logPath |
||||
|
|
||||
|
$stepSummary = $env:GITHUB_STEP_SUMMARY |
||||
|
$env:GITHUB_STEP_SUMMARY = $summaryPath |
||||
|
try { |
||||
|
"Running the tests as a basic user: $commandLine" |
||||
|
$exitCode = [NTFSSecurityBasicUserProcess]::Run((Join-Path -Path $env:SystemRoot -ChildPath 'System32\cmd.exe'), $commandLine, $repositoryPath) |
||||
|
} |
||||
|
finally { |
||||
|
$env:GITHUB_STEP_SUMMARY = $stepSummary |
||||
|
} |
||||
|
|
||||
|
if (Test-Path -LiteralPath $logPath) { |
||||
|
Get-Content -LiteralPath $logPath |
||||
|
} |
||||
|
|
||||
|
if ($stepSummary -and (Test-Path -LiteralPath $summaryPath)) { |
||||
|
$encoding = New-Object -TypeName 'System.Text.UTF8Encoding' -ArgumentList $false |
||||
|
[IO.File]::AppendAllText($stepSummary, [IO.File]::ReadAllText($summaryPath), $encoding) |
||||
|
} |
||||
|
|
||||
|
if (Test-Path -LiteralPath $runResultPath) { |
||||
|
Copy-Item -LiteralPath $runResultPath -Destination $resultFullPath -Force |
||||
|
} |
||||
|
|
||||
|
Remove-Item -LiteralPath $runFolder -Recurse -Force |
||||
|
if ($exitCode -ne 0) { |
||||
|
throw "The tests as a basic user failed with exit code $exitCode." |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
--- |
||||
|
status: accepted |
||||
|
date: 2026-10-08 |
||||
|
last-verified: 2026-10-08 |
||||
|
owner: shared |
||||
|
source: maintainer decision of 2026-10-08 |
||||
|
--- |
||||
|
|
||||
|
# Decision 21: A quality gate before 5.0.0 |
||||
|
|
||||
|
- Choice: 5.0.0 ships only at the highest quality, with everything tested |
||||
|
(maintainer, 2026-10-08). The gate has three phases: |
||||
|
1. Measure 5.0.0-rc5: done on 2026-10-08 (`progress.md`). |
||||
|
2. Add tests until every code path is tested or explained, live tests |
||||
|
for the remaining cmdlets, and test-first fixes of the known defects; |
||||
|
release them as 5.0.0-rc6. The maintainer approved it on 2026-10-08. |
||||
|
3. Run the live tests on more operating systems, such as a Windows 11 |
||||
|
client and Server 2019 and 2022 file servers, then release 5.0.0. |
||||
|
- Exit criteria for 5.0.0, as proposed on 2026-10-08: |
||||
|
- Every cmdlet and parameter set has behavior tests, error paths |
||||
|
included. |
||||
|
- No test is skipped in every configuration that runs. |
||||
|
- The C# coverage is measured, and every path that no test runs is |
||||
|
tested or explained. |
||||
|
- Every known defect is fixed, or accepted by the maintainer and listed |
||||
|
in the release notes. |
||||
|
- The published package passes the live tests on every operating system |
||||
|
of the matrix. |
||||
|
- Rationale: rc5 passed every test that ran, but the tests ran 55.9% of |
||||
|
the code lines and 37.4% of the branches; five cmdlets had no tests of |
||||
|
their own, and 19 cmdlets never ran over SMB. (That measurement counted |
||||
|
only one of the four test runs; with all four, rc5 runs 58.1% of the |
||||
|
lines and 38.0% of the branches, see `techContext.md`.) |
||||
|
- Open: behavior changes found on the way stay the maintainer's decision |
||||
|
(Decision 16); so do the 244 lines of classes that no cmdlet calls, the |
||||
|
operating systems of Phase 3, and how to cover file servers that aren't |
||||
|
Windows (#34). |
||||
@ -0,0 +1,149 @@ |
|||||
|
# Lab acceptance of 5.0.0-rc6 |
||||
|
|
||||
|
Acceptance of the release candidate 5.0.0-rc6 in the lab, on 2026-10-08, |
||||
|
before the pull request. It follows the procedure in the |
||||
|
[README](README.md#acceptance-of-a-release-candidate). |
||||
|
|
||||
|
The candidate changed twice during the acceptance. The run of `acfe3af` |
||||
|
passed; the coverage report of the candidate then found three defects, |
||||
|
which `0df2482` and `1b9edbb` fix, and the run of `1b9edbb` passed as |
||||
|
well; a second review led to `7b0781f`. This record describes the run of |
||||
|
`7b0781f`, the last commit of the pull request that changes the module, |
||||
|
and names the results of the earlier runs. |
||||
|
|
||||
|
## Candidate |
||||
|
|
||||
|
- Branch `ai/release-5.0.0-rc6`, commit |
||||
|
`7b0781ff8bb2c1ee4087a16411acd4c7070ba1fa`, 31 commits on `fcb370e` |
||||
|
(5.0.0-rc5). |
||||
|
- Release build of that commit, packaged with |
||||
|
`.github\scripts\New-ModulePackage.ps1`. The live tests imported the |
||||
|
module from the extracted `NTFSSecurity.zip`. CI builds the packages that |
||||
|
the tag publishes again, so their hashes differ; the live tests run once |
||||
|
more against the published package. |
||||
|
|
||||
|
| SHA-256 | File | |
||||
|
| --- | --- | |
||||
|
| `E99B5123F45E4F56AC005C629C2241DC616B2AAC152C17EA57A67C0823FFCA10` | `NTFSSecurity.5.0.0-rc6.nupkg` | |
||||
|
| `53C020EAD59467A407ED755F3D9296E9C70AFFAB184CF9CDF27AF92117FBBEE0` | `NTFSSecurity.zip` | |
||||
|
| `F438D7FDB3F5A1D75F5CA48D7B610EED31215FF1BF9C185C6856AA365D195C8D` | `NTFSSecurity\NTFSSecurity.dll` | |
||||
|
| `EE1B0DF9619C998A4482F3F79DCB2191BBEAD859660D6D924D1F333DBE7CBBCE` | `NTFSSecurity\Security2.dll` | |
||||
|
| `902157ABBD2E0B76DA744A918BDD174D5226C3494908ABA75F9E5DE28AE6A008` | `NTFSSecurity\ProcessPrivileges.dll` | |
||||
|
| `E2077AFEB38703345AE7857C1266F8B26E167ED887BFFAC8C8169A8F267BE6E9` | `NTFSSecurity\PrivilegeControl.dll` | |
||||
|
| `A8DA47194AB0F71232C69D01955AD93BA73C7ECEB58D0DE800CA085D4A2E18D8` | `NTFSSecurity\AlphaFS.dll` | |
||||
|
| `75DA9F7A54DF7011968BACB3FDF5E30B33F4C078861D1DF87BC06F5E64A962D8` | `NTFSSecurity\NTFSSecurity.psd1` | |
||||
|
| `3F777E9D141EE0046119DA9D5ECF88A3BC7E023726098FE2FB150528E2FB59B8` | `NTFSSecurity\NTFSSecurity.psm1` | |
||||
|
| `59583423241951EBE0FC2D8237D0C28C3ECC8C7CD2C115D2660F8579888632FC` | `NTFSSecurity\NTFSSecurity.Init.ps1` | |
||||
|
| `FB0920CC37ED858F55AFD54998DC854E27FBBE6A0177CB059CB03CFE91361197` | `NTFSSecurity\NTFSSecurity.format.ps1xml` | |
||||
|
| `CB6882FF91E6716605D5599E7B464C3346E461216ACED07E847621738F04FB9B` | `NTFSSecurity\NTFSSecurity.types.ps1xml` | |
||||
|
| `5115D0D76CA2A06795CD754539AC7EC19A70591E8C5616E7BEB5AE66E6971E6D` | `NTFSSecurity\en-US\NTFSSecurity.dll-Help.xml` | |
||||
|
|
||||
|
## Tests without a lab |
||||
|
|
||||
|
The Pester suite of the commit, 684 tests, against the same build. No test |
||||
|
failed, and every test ran in at least one configuration. |
||||
|
|
||||
|
| Configuration | Passed | Failed | Skipped | |
||||
|
| --- | ---: | ---: | ---: | |
||||
|
| Windows PowerShell 5.1, elevated | 662 | 0 | 22 | |
||||
|
| PowerShell 7, elevated | 632 | 0 | 52 | |
||||
|
| Windows PowerShell 5.1, basic user | 590 | 0 | 94 | |
||||
|
| PowerShell 7, basic user | 560 | 0 | 124 | |
||||
|
|
||||
|
The C# coverage of the four configurations, measured with AltCover on |
||||
|
`1b9edbb`: 68.1% of the lines and 44.3% of the branches. |
||||
|
|
||||
|
## Lab |
||||
|
|
||||
|
`WindowsAccessControlLab` (AutomatedLab on Hyper-V). Every machine runs |
||||
|
Windows Server 2025 Datacenter (10.0.26100). |
||||
|
|
||||
|
| Machine | Domain | Role in the tests | |
||||
|
| --- | --- | --- | |
||||
|
| `F1ADC1` | `a.forest1.net` | Domain controller of the accounts | |
||||
|
| `F1AFile1` | `a.forest1.net` | Client that runs the tests | |
||||
|
| `F1AFile2` | `a.forest1.net` | File server with the share | |
||||
|
| `F1BDC1` | `b.forest1.net` | Account of another domain of the forest | |
||||
|
| `F2DC1` | `forest2.net` | Account of another forest | |
||||
|
| `F3DC1` | `forest3.net` | Account of another forest | |
||||
|
|
||||
|
Readiness, checked before each run: WinRM answered on all six machines. |
||||
|
The four domain controllers answered LDAP (RootDSE, synchronized) and |
||||
|
issued a Kerberos ticket for `krbtgt`. The client and the file server |
||||
|
found a domain controller, had a working secure channel, and got a service |
||||
|
ticket for each other. The clocks were 4.3 to 5.0 seconds ahead of the |
||||
|
host. |
||||
|
|
||||
|
Checkpoints (Production) of the six machines, taken before each run: |
||||
|
`ntfs-rc6-acfe3af-before-acceptance` (11:07 UTC), |
||||
|
`ntfs-rc6-1b9edbb-before-acceptance` (12:21 UTC), and |
||||
|
`ntfs-rc6-7b0781f-before-acceptance` (12:50 UTC). |
||||
|
|
||||
|
## Results |
||||
|
|
||||
|
`Invoke-NTFSSecurityLabTest.ps1 -ModulePath <extracted package>` in both |
||||
|
editions, 12:51 to 13:07 UTC. The module reported version 5.0.0-rc6 in |
||||
|
every role that loads it. |
||||
|
|
||||
|
| Edition | Role | Passed | Failed | Skipped | |
||||
|
| --- | --- | ---: | ---: | ---: | |
||||
|
| Windows PowerShell 5.1 | Delegate | 38 | 0 | 0 | |
||||
|
| Windows PowerShell 5.1 | ServerAdmin | 13 | 0 | 0 | |
||||
|
| Windows PowerShell 5.1 | Admin | 40 | 0 | 0 | |
||||
|
| Windows PowerShell 5.1 | Server | 72 | 0 | 1 | |
||||
|
| PowerShell 7 | Delegate | 38 | 0 | 0 | |
||||
|
| PowerShell 7 | ServerAdmin | 13 | 0 | 0 | |
||||
|
| PowerShell 7 | Admin | 40 | 0 | 0 | |
||||
|
| PowerShell 7 | Server | 72 | 0 | 1 | |
||||
|
|
||||
|
The role Server skips the check of the module version, because it doesn't |
||||
|
load the module. The accounts of the other domain and forests were |
||||
|
`B\NtfsLiveForeign`, `forest2\NtfsLiveForeign`, and |
||||
|
`forest3\NtfsLiveForeign`; their 13 tests passed in both editions. |
||||
|
|
||||
|
The earlier runs had the same counts and no failure: `acfe3af` from 11:08 |
||||
|
to 11:25 UTC, and `1b9edbb` from 12:23 to 12:39 UTC. |
||||
|
|
||||
|
## Baseline |
||||
|
|
||||
|
The published 5.0.0-rc5 from the PowerShell Gallery, whose hash the script |
||||
|
checks against the one the Gallery publishes, in Windows PowerShell 5.1, |
||||
|
11:26 to 11:34 UTC: Delegate 38 passed, ServerAdmin 13, Admin 38 passed and |
||||
|
2 failed, Server 72 passed and 1 skipped. The two failures are the defect |
||||
|
that 5.0.0-rc6 fixes: on the share, `Get-NTFSHardLink` and |
||||
|
`New-NTFSHardLink -PassThru` stopped with the terminating error "(50) The |
||||
|
request is not supported" instead of writing a `GetHardLinkError`. The new |
||||
|
cases find no other difference between the two versions; the local tests |
||||
|
cover the other fixes of 5.0.0-rc6. |
||||
|
|
||||
|
## Cleanup |
||||
|
|
||||
|
`Invoke-NTFSSecurityLabTest.ps1 -RemoveFixture` after each run: at 11:38 |
||||
|
UTC after the first run and the baseline, at 12:48 UTC after the second, |
||||
|
and at 13:09 UTC after the third. Each check compared the lab with the 10 |
||||
|
SIDs of the fixture's accounts and groups, read before the removal; the |
||||
|
same check had found the fixture before the first removal. After each |
||||
|
removal: |
||||
|
|
||||
|
- No domain has the organizational unit `NTFSSecurityLive` or an account |
||||
|
whose name starts with `NtfsLive`. |
||||
|
- The file server has no share `NTFSSecurityLive`, no folder |
||||
|
`C:\NTFSSecurityLive` or `C:\NTFSSecurityLab`, and no local group |
||||
|
`NtfsLiveLocal`. |
||||
|
- The client has no folder `C:\NTFSSecurityLab`. |
||||
|
- On both machines, Administrators, Access Control Assistance Operators, |
||||
|
and Remote Management Users have no member of the fixture, and no |
||||
|
profile of the fixture's accounts is left. |
||||
|
|
||||
|
The three checkpoints stay on the six machines until the maintainer |
||||
|
deletes them. |
||||
|
|
||||
|
## Not covered |
||||
|
|
||||
|
- Other operating systems than Windows Server 2025, such as a Windows 11 |
||||
|
client and Server 2019 or 2022 file servers: Phase 3 of the quality |
||||
|
gate. |
||||
|
- File servers that aren't Windows, such as the IBM ESS system of #34: |
||||
|
only the feedback of the reporter covers them. |
||||
|
- The package that CI publishes for the tag: the live tests run against it |
||||
|
after the release, with `-Version 5.0.0-rc6`. |
||||
@ -0,0 +1,232 @@ |
|||||
|
<# |
||||
|
Tests the error handling that the cmdlets with -Path share, with the module built in NTFSSecurity\bin\Release on |
||||
|
files in a sandbox folder: a path that doesn't exist, an item whose owner may not read its permissions, and an item |
||||
|
whose owner may not change its permissions, which the cmdlets that write the DACL handle by taking ownership. Each |
||||
|
error belongs to its path only, and the cmdlet continues with the next one. The tests turn the module setting |
||||
|
EnablePrivileges off, so that an elevated session meets the same denials as a basic user, and restore it. |
||||
|
#> |
||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
||||
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' |
||||
|
)] |
||||
|
param () |
||||
|
|
||||
|
BeforeDiscovery { |
||||
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force |
||||
|
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege' |
||||
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value |
||||
|
$readEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData' } |
||||
|
# An audit entry on a file has no inheritance flags. |
||||
|
$auditEntry = @{ Account = 'S-1-1-0'; AccessRights = 'ReadData'; InheritanceFlags = 'None'; PropagationFlags = 'None' } |
||||
|
|
||||
|
# Output: whether the cmdlet writes an object for the next path, an existing file. |
||||
|
$missingPathCases = @( |
||||
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Add-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Remove-NTFSAccess'; Parameters = $readEntry; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Clear-NTFSAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Enable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Disable-NTFSAccessInheritance'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Set-NTFSInheritance'; Parameters = @{ AccessInheritanceEnabled = $true }; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Set-NTFSOwner'; Parameters = @{ Account = $currentUser }; ErrorId = 'ReadFileError'; Output = $false } |
||||
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-Item2'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true } |
||||
|
@{ Command = 'Get-FileHash2'; Parameters = @{}; ErrorId = 'ReadFileError'; Output = $true } |
||||
|
@{ Command = 'Get-NTFSHardLink'; Parameters = @{}; ErrorId = 'FileNotFound'; Output = $true } |
||||
|
) |
||||
|
|
||||
|
# The audit cmdlets need the Security privilege for the next path. |
||||
|
$missingPathAuditCases = @( |
||||
|
@{ Command = 'Get-NTFSAudit'; Parameters = @{} } |
||||
|
@{ Command = 'Add-NTFSAudit'; Parameters = $auditEntry } |
||||
|
@{ Command = 'Remove-NTFSAudit'; Parameters = $auditEntry } |
||||
|
@{ Command = 'Clear-NTFSAudit'; Parameters = @{} } |
||||
|
@{ Command = 'Enable-NTFSAuditInheritance'; Parameters = @{} } |
||||
|
@{ Command = 'Disable-NTFSAuditInheritance'; Parameters = @{} } |
||||
|
) |
||||
|
|
||||
|
$deniedReadCases = @( |
||||
|
@{ Command = 'Get-NTFSAccess'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSEffectiveAccess'; Parameters = @{ Account = 'S-1-1-0' }; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOrphanedAccess'; Parameters = @{}; Output = $false } |
||||
|
@{ Command = 'Get-NTFSInheritance'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSOwner'; Parameters = @{}; Output = $true } |
||||
|
@{ Command = 'Get-NTFSSecurityDescriptor'; Parameters = @{}; Output = $true } |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
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 'PathErrors' |
||||
|
Push-Location -LiteralPath $sandbox |
||||
|
|
||||
|
$privateData = (Get-Module -Name NTFSSecurity).PrivateData |
||||
|
$enablePrivileges = $privateData['EnablePrivileges'] |
||||
|
$privateData['EnablePrivileges'] = $false |
||||
|
$sidType = [System.Security.Principal.SecurityIdentifier] |
||||
|
|
||||
|
function Get-TestMissingPath { |
||||
|
$path = Join-Path -Path $sandbox -ChildPath ('Missing-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8)) |
||||
|
Assert-TestSandboxPath -Sandbox $sandbox -Path $path |
||||
|
$path |
||||
|
} |
||||
|
|
||||
|
function Get-TestAcl { |
||||
|
# .NET, because Get-Acl of an elevated Windows PowerShell reads also items that deny reading their permissions. |
||||
|
# .NET Core has the method as an extension method. |
||||
|
param ([string] $Path) |
||||
|
|
||||
|
$info = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList $Path |
||||
|
if ($PSVersionTable.PSEdition -eq 'Desktop') { |
||||
|
$info.GetAccessControl() |
||||
|
} |
||||
|
else { |
||||
|
[System.IO.FileSystemAclExtensions]::GetAccessControl($info) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
AfterAll { |
||||
|
$privateData['EnablePrivileges'] = $enablePrivileges |
||||
|
Pop-Location |
||||
|
Remove-TestSandbox -Sandbox $sandbox |
||||
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue |
||||
|
} |
||||
|
|
||||
|
Describe 'A path that does not exist' { |
||||
|
It '<Command> should write a <ErrorId> for it and continue with the next path' -ForEach $missingPathCases { |
||||
|
$missing = Get-TestMissingPath |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Next' |
||||
|
|
||||
|
$output = @(& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike "$ErrorId,*" |
||||
|
$pathErrors[0].TargetObject | Should -Be $missing |
||||
|
if ($Output) { |
||||
|
$output | Should -Not -BeNullOrEmpty |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
It '<Command> should write a ReadFileError for it and continue with the next path' -ForEach $missingPathAuditCases -Skip:(-not $holdsSecurityPrivilege) { |
||||
|
$privateData['EnablePrivileges'] = $true |
||||
|
try { |
||||
|
$missing = Get-TestMissingPath |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextAudit' |
||||
|
|
||||
|
& $Command -Path $missing, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue | Out-Null |
||||
|
} |
||||
|
finally { |
||||
|
$privateData['EnablePrivileges'] = $false |
||||
|
} |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadFileError,*' |
||||
|
$pathErrors[0].TargetObject | Should -Be $missing |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'An item whose owner may not read its permissions' { |
||||
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to read the security descriptor. Taking ownership |
||||
|
# can't help: the cmdlet must read the owner first, which needs the same right. Before 5.0.0-rc6, |
||||
|
# Get-NTFSOrphanedAccess reported this as an AddAceError. |
||||
|
It '<Command> should write a ReadSecurityError for it and continue with the next path' -ForEach $deniedReadCases { |
||||
|
$blocked = New-TestSandboxItem -Sandbox $sandbox -Name 'Unreadable' |
||||
|
Block-TestReadPermission -Sandbox $sandbox -Path $blocked |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'NextReadable' |
||||
|
|
||||
|
$output = @(& $Command -Path $blocked, $file @Parameters -ErrorVariable pathErrors -ErrorAction SilentlyContinue -WarningAction SilentlyContinue) |
||||
|
|
||||
|
$pathErrors | Should -HaveCount 1 |
||||
|
$pathErrors[0].FullyQualifiedErrorId | Should -BeLike 'ReadSecurityError,*' |
||||
|
$pathErrors[0].TargetObject | Should -Be $blocked |
||||
|
if ($Output) { |
||||
|
$output | Should -Not -BeNullOrEmpty |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'An item whose owner may not change its permissions' { |
||||
|
# A deny entry for OWNER RIGHTS replaces the right of the owner to change the DACL. The cmdlets take ownership, |
||||
|
# which Windows answers by removing the OWNER RIGHTS entries, write the DACL, and set the previous owner back. |
||||
|
BeforeEach { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Unchangeable' |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$owner = $acl.GetOwner($sidType).Value |
||||
|
} |
||||
|
|
||||
|
It 'Add-NTFSAccess should take ownership, add the entry, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -HaveCount 1 |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-3-4' }) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Remove-NTFSAccess should take ownership, remove the entry, and set the owner back' { |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Remove-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType) | Where-Object -FilterScript { $_.IdentityReference.Value -eq 'S-1-1-0' }) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Clear-NTFSAccess should take ownership, remove the explicit entries, and set the owner back' { |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Clear-NTFSAccess -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
@($acl.GetAccessRules($true, $false, $sidType)) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Disable-NTFSAccessInheritance should take ownership, protect the DACL, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Disable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
} |
||||
|
|
||||
|
It 'Enable-NTFSAccessInheritance should take ownership, let the DACL inherit, and set the owner back' { |
||||
|
Disable-NTFSAccessInheritance -Path $file |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Enable-NTFSAccessInheritance -Path $file -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeFalse |
||||
|
} |
||||
|
|
||||
|
It 'Set-NTFSInheritance should take ownership, protect the DACL, and set the owner back' { |
||||
|
Add-TestDenyRule -Sandbox $sandbox -Path $file -Rights @{ 'S-1-3-4' = 'ChangePermissions' } |
||||
|
|
||||
|
Set-NTFSInheritance -Path $file -AccessInheritanceEnabled $false -ErrorVariable changeErrors -ErrorAction SilentlyContinue |
||||
|
|
||||
|
$changeErrors | Should -BeNullOrEmpty |
||||
|
$acl = Get-TestAcl -Path $file |
||||
|
$acl.GetOwner($sidType).Value | Should -Be $owner |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
<# |
||||
|
Tests the SecurityDescriptor parameter sets that no other test file covers, with the module built in |
||||
|
NTFSSecurity\bin\Release on files in a sandbox folder. The cmdlets change a descriptor of Get-NTFSSecurityDescriptor |
||||
|
in memory only, and the item changes when Set-NTFSSecurityDescriptor writes the descriptor; the cmdlets that read |
||||
|
return what their Path parameter set returns. Tests of audit entries need the Security privilege and skip without |
||||
|
it. |
||||
|
#> |
||||
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute( |
||||
|
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.' |
||||
|
)] |
||||
|
param () |
||||
|
|
||||
|
BeforeDiscovery { |
||||
|
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force |
||||
|
$holdsSecurityPrivilege = Test-PrivilegeHeld -Name 'SeSecurityPrivilege' |
||||
|
} |
||||
|
|
||||
|
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 'SecurityDescriptorSets' |
||||
|
Push-Location -LiteralPath $sandbox |
||||
|
$sidType = [System.Security.Principal.SecurityIdentifier] |
||||
|
|
||||
|
function Get-ExplicitAccessCount { |
||||
|
param ([System.Security.AccessControl.FileSystemSecurity] $Acl) |
||||
|
|
||||
|
@($Acl.GetAccessRules($true, $false, $sidType)).Count |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
AfterAll { |
||||
|
Pop-Location |
||||
|
Remove-TestSandbox -Sandbox $sandbox |
||||
|
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue |
||||
|
} |
||||
|
|
||||
|
Describe 'Cmdlets that change a security descriptor in memory' { |
||||
|
It 'Clear-NTFSAccess should remove the explicit access entries of the descriptor' { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearAccess' |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
|
||||
|
Clear-NTFSAccess -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
|
||||
|
Get-ExplicitAccessCount -Acl $sd.SecurityDescriptor | Should -Be 0 |
||||
|
Get-ExplicitAccessCount -Acl (Get-Acl -LiteralPath $file) | Should -Be 1 |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
Get-ExplicitAccessCount -Acl (Get-Acl -LiteralPath $file) | Should -Be 0 |
||||
|
} |
||||
|
|
||||
|
# Like the Path parameter set, the cmdlet doesn't copy the inherited entries, so the DACL ends up empty. |
||||
|
It 'Clear-NTFSAccess -DisableInheritance should leave the descriptor with an empty, protected DACL' { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearAccessProtected' |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
$daclBefore = (Get-Acl -LiteralPath $file).GetSecurityDescriptorSddlForm('Access') |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
|
||||
|
Clear-NTFSAccess -SecurityDescriptor $sd -DisableInheritance -ErrorAction Stop |
||||
|
|
||||
|
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeTrue |
||||
|
@($sd.SecurityDescriptor.GetAccessRules($true, $true, $sidType)) | Should -BeNullOrEmpty |
||||
|
(Get-Acl -LiteralPath $file).GetSecurityDescriptorSddlForm('Access') | Should -Be $daclBefore |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
$acl = Get-Acl -LiteralPath $file |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
@($acl.GetAccessRules($true, $true, $sidType)) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Disable-NTFSAccessInheritance should protect the DACL of the descriptor and keep the inherited entries' { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'DisableAccess' |
||||
|
$inheritedCount = @((Get-Acl -LiteralPath $file).GetAccessRules($false, $true, $sidType)).Count |
||||
|
$inheritedCount | Should -BeGreaterThan 0 |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
|
||||
|
Disable-NTFSAccessInheritance -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
|
||||
|
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeTrue |
||||
|
(Get-Acl -LiteralPath $file).AreAccessRulesProtected | Should -BeFalse |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
$acl = Get-Acl -LiteralPath $file |
||||
|
$acl.AreAccessRulesProtected | Should -BeTrue |
||||
|
Get-ExplicitAccessCount -Acl $acl | Should -Be $inheritedCount |
||||
|
} |
||||
|
|
||||
|
It 'Enable-NTFSAccessInheritance should let the DACL of the descriptor inherit' { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'EnableAccess' |
||||
|
Disable-NTFSAccessInheritance -Path $file -RemoveInheritedAccessRules |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
|
||||
|
Enable-NTFSAccessInheritance -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
|
||||
|
$sd.SecurityDescriptor.AreAccessRulesProtected | Should -BeFalse |
||||
|
(Get-Acl -LiteralPath $file).AreAccessRulesProtected | Should -BeTrue |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
$acl = Get-Acl -LiteralPath $file |
||||
|
$acl.AreAccessRulesProtected | Should -BeFalse |
||||
|
@($acl.GetAccessRules($false, $true, $sidType)) | Should -Not -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Clear-NTFSAudit should remove the explicit audit entries of the descriptor' -Skip:(-not $holdsSecurityPrivilege) { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'ClearAudit' |
||||
|
Add-NTFSAudit -Path $file -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
|
||||
|
Clear-NTFSAudit -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
|
||||
|
@($sd.SecurityDescriptor.GetAuditRules($true, $false, $sidType)) | Should -BeNullOrEmpty |
||||
|
@(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -HaveCount 1 |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
@(Get-NTFSAudit -Path $file -ExcludeInherited) | Should -BeNullOrEmpty |
||||
|
} |
||||
|
|
||||
|
It 'Enable-NTFSAuditInheritance should let the SACL of the descriptor inherit' -Skip:(-not $holdsSecurityPrivilege) { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'EnableAudit' |
||||
|
Disable-NTFSAuditInheritance -Path $file |
||||
|
$sd = Get-NTFSSecurityDescriptor -Path $file |
||||
|
$sd.SecurityDescriptor.AreAuditRulesProtected | Should -BeTrue |
||||
|
|
||||
|
Enable-NTFSAuditInheritance -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
|
||||
|
$sd.SecurityDescriptor.AreAuditRulesProtected | Should -BeFalse |
||||
|
(Get-NTFSInheritance -Path $file).AuditInheritanceEnabled | Should -BeFalse |
||||
|
Set-NTFSSecurityDescriptor -SecurityDescriptor $sd -ErrorAction Stop |
||||
|
(Get-NTFSInheritance -Path $file).AuditInheritanceEnabled | Should -BeTrue |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Describe 'Cmdlets that read a security descriptor in memory' { |
||||
|
BeforeAll { |
||||
|
$file = New-TestSandboxItem -Sandbox $sandbox -Name 'Read' |
||||
|
Add-NTFSAccess -Path $file -Account 'S-1-1-0' -AccessRights ReadData |
||||
|
} |
||||
|
|
||||
|
It 'Get-NTFSAccess should return the entries that it returns for the path' { |
||||
|
$expected = @(Get-NTFSAccess -Path $file | ForEach-Object -Process { '{0}|{1}|{2}' -f $_.Account.Sid, $_.AccessRights, $_.IsInherited }) |
||||
|
|
||||
|
$result = @(Get-NTFSSecurityDescriptor -Path $file | Get-NTFSAccess -ErrorAction Stop) |
||||
|
|
||||
|
$result | Should -Not -BeNullOrEmpty |
||||
|
($result | ForEach-Object -Process { '{0}|{1}|{2}' -f $_.Account.Sid, $_.AccessRights, $_.IsInherited }) -join ';' | Should -Be ($expected -join ';') |
||||
|
$result | ForEach-Object -Process { $_.FullName | Should -Be $file } |
||||
|
} |
||||
|
|
||||
|
It 'Get-NTFSOwner should return the owner that it returns for the path' { |
||||
|
$expected = (Get-NTFSOwner -Path $file).Owner.Sid |
||||
|
|
||||
|
$result = @(Get-NTFSSecurityDescriptor -Path $file | Get-NTFSOwner -ErrorAction Stop) |
||||
|
|
||||
|
$result | Should -HaveCount 1 |
||||
|
$result[0].Owner.Sid | Should -Be $expected |
||||
|
$result[0].FullName | Should -Be $file |
||||
|
} |
||||
|
|
||||
|
It 'Get-NTFSAudit should return the audit entries that it returns for the path' -Skip:(-not $holdsSecurityPrivilege) { |
||||
|
Add-NTFSAudit -Path $file -Account 'S-1-1-0' -AccessRights ReadData -InheritanceFlags None -PropagationFlags None |
||||
|
$expected = @(Get-NTFSAudit -Path $file | ForEach-Object -Process { '{0}|{1}|{2}' -f $_.Account.Sid, $_.AccessRights, $_.AuditFlags }) |
||||
|
|
||||
|
$result = @(Get-NTFSSecurityDescriptor -Path $file | Get-NTFSAudit -ErrorAction Stop) |
||||
|
|
||||
|
$result | Should -HaveCount 1 |
||||
|
($result | ForEach-Object -Process { '{0}|{1}|{2}' -f $_.Account.Sid, $_.AccessRights, $_.AuditFlags }) -join ';' | Should -Be ($expected -join ';') |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue