Test/New-LocalAdminUser.Tests.ps1
|
using namespace System using namespace System.IO using namespace System.Security.Cryptography using namespace System.Collections.Immutable Set-StrictMode -Version Latest # Evaluated at discovery time so -Skip resolves while Pester builds the tree. $IsElevatedHost = $false if ($IsWindows) { $principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() $IsElevatedHost = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } BeforeAll { $moduleRoot = Split-Path -Path $PSScriptRoot -Parent Import-Module (Join-Path -Path $moduleRoot -ChildPath 'PSSecurity.psd1') -Force } AfterAll { Remove-Module PSSecurity -Force -ErrorAction SilentlyContinue } #### <h2 style="color: #DCA657;">New-LocalAdminUser</h2> #### #### Skipped off Windows. Local accounts and the Administrators group are Windows concepts. #### Describe 'New-LocalAdminUser' -Skip:(-not $IsWindows) { #### #### <b style="color: #D2A8FF;">Cases</b> #### #### - The function is exported, and is a function rather than an alias. It 'Is exported as a function' { $cmd = Get-Command -Module PSSecurity -Name 'New-LocalAdminUser' -ErrorAction SilentlyContinue $cmd | Should -Not -BeNullOrEmpty $cmd.CommandType | Should -Be 'Function' } } #### #### --- #### #### <h2 style="color: #DCA657;">New-LocalAdminUser elevated</h2> #### #### The account is removed from Administrators and deleted in `AfterAll`, each in #### its own `try` so a failure in the first does not skip the second. #### Describe 'New-LocalAdminUser elevated' -Skip:(-not $IsElevatedHost) { BeforeAll { $script:userName = '_OptSecTest_' + (Get-Random -Maximum 999999).ToString('D6') # Appended one character at a time so the password never exists as a # plaintext string. Local account policy wants several character classes, # so one of each leads rather than being left to the random fill. $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $password = [System.Security.SecureString]::new() foreach ($required in 'Q', 'z', '7', '!') { $password.AppendChar($required) } foreach ($position in 1..20) { $password.AppendChar($alphabet[[RandomNumberGenerator]::GetInt32(0, $alphabet.Length)]) } $password.MakeReadOnly() $script:cred = [PSCredential]::new($script:userName, $password) } AfterAll { # Each removal gets its own try so a failure in the first does not skip the # second. The catch warns rather than swallowing: a sacrificial admin account # left behind on the machine is worth saying out loud, and throwing here # would fail the run over cleanup that already did its real work. try { Remove-LocalGroupMember -Group 'Administrators' -Member $script:userName -ErrorAction SilentlyContinue } catch { Write-Warning "Could not remove $($script:userName) from Administrators: $($_.Exception.Message)" } try { Remove-LocalUser -Name $script:userName -ErrorAction SilentlyContinue } catch { Write-Warning "Could not remove the local account $($script:userName): $($_.Exception.Message)" } } #### #### <b style="color: #D2A8FF;">Cases</b> #### #### - The account is created, reported, and is a real member of Administrators. #### The group membership is verified against Windows, not against the return value. It 'Creates the user and adds it to Administrators' { $newAdmin = @{ Credential = $script:cred FullName = 'PSSecurity PSSecurity Test' Description = 'Sacrificial test account; safe to delete' } $result = New-LocalAdminUser @newAdmin $result.Username | Should -Be $script:userName $result.Created | Should -BeTrue $result.IsAdministrator | Should -BeTrue Get-LocalUser -Name $script:userName -ErrorAction Stop | Should -Not -BeNullOrEmpty $member = Get-LocalGroupMember -Group 'Administrators' | Where-Object { $_.Name -like "*\$($script:userName)" } $member | Should -Not -BeNullOrEmpty } } #### #### --- #### |