src/Public/Collect/Invoke-FotonUserCollection.ps1
|
# Invoke-FotonUserCollection - accounts, groups, sessions, RDP, credentials. function Invoke-FotonUserCollection { Write-FotonStatus 'Collecting accounts, groups, sessions, and access configuration' 'Step' Invoke-FotonCollection -Category 'Users' -Name 'local_users' -Format 'Csv' -Description 'Local user accounts and password state' -Script { $users = @() try { $users = Get-LocalUser -ErrorAction Stop | Select-Object Name, Enabled, SID, Description, @{n = 'LastLogon'; e = { if ($_.LastLogon) { $_.LastLogon.ToString('yyyy-MM-dd HH:mm:ss') } else { 'Never' } } }, @{n = 'PasswordLastSet'; e = { if ($_.PasswordLastSet) { $_.PasswordLastSet.ToString('yyyy-MM-dd HH:mm:ss') } else { 'Never' } } }, PasswordRequired, PasswordExpires, UserMayChangePassword, @{n = 'AccountExpires'; e = { if ($_.AccountExpires) { $_.AccountExpires.ToString('yyyy-MM-dd') } else { 'Never' } } } } catch { $users = Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction Stop | Select-Object Name, @{n = 'Enabled'; e = { -not $_.Disabled } }, SID, Description, @{n = 'LastLogon'; e = { '' } }, @{n = 'PasswordLastSet'; e = { '' } }, @{n = 'PasswordRequired'; e = { $_.PasswordRequired } }, @{n = 'PasswordExpires'; e = { $_.PasswordExpires } }, @{n = 'UserMayChangePassword'; e = { $_.PasswordChangeable } }, @{n = 'AccountExpires'; e = { '' } } } $users } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'local_group_membership' -Format 'Csv' -Description 'Members of every local group (privilege mapping)' -Script { $rows = @() try { foreach ($g in (Get-LocalGroup -ErrorAction Stop)) { $members = @() $enumError = $null # Empty catch used to write Member='(none)' when Get-LocalGroupMember threw # (common on domain-joined hosts with domain principals / orphaned SIDs), # which hid real membership - including Administrators - behind Status Success. $source = 'Cmdlet' try { $members = @(Get-LocalGroupMember -Group $g.Name -ErrorAction Stop) } catch { $enumError = $_.Exception.Message } if ($enumError) { try { $adsi = @(Get-FotonLocalGroupMemberAdsi -GroupName $g.Name) $members = @($adsi | ForEach-Object { [PSCustomObject]@{ Name = $_.Member; ObjectClass = $_.ObjectClass; PrincipalSource = 'ADSI'; SID = $_.SID } }) $source = 'ADSI' $enumError = $null } catch { $enumError = ('{0}; ADSI fallback: {1}' -f $enumError, $_.Exception.Message) } } if ($enumError) { $rows += [PSCustomObject]@{ Group = $g.Name Member = ('(error: {0})' -f $enumError) ObjectClass = '' PrincipalSource = '' SID = '' } } elseif ($members.Count -eq 0) { $rows += [PSCustomObject]@{ Group = $g.Name; Member = '(none)'; ObjectClass = ''; PrincipalSource = $source; SID = '' } } foreach ($m in $members) { $rows += [PSCustomObject]@{ Group = $g.Name Member = $m.Name ObjectClass = [string]$m.ObjectClass PrincipalSource = [string]$m.PrincipalSource SID = [string]$m.SID } } } } catch { $rows += [PSCustomObject]@{ Group = 'ERROR'; Member = $_.Exception.Message; ObjectClass = ''; PrincipalSource = ''; SID = '' } } $rows } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'user_profiles' -Format 'Csv' -Description 'Profiles present on disk with SID mapping' -Script { Get-FotonUserProfile } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'logged_on_sessions' -Format 'Txt' -Description 'Current interactive and remote sessions' -Script { $out = @() $out += '--- query user ---' $out += (& query.exe user 2>&1 | Out-String) $out += '--- query session ---' $out += (& query.exe session 2>&1 | Out-String) $out += '--- Win32_LogonSession (interactive/remote) ---' try { $out += (Get-CimInstance Win32_LogonSession -ErrorAction Stop | Select-Object LogonId, LogonType, AuthenticationPackage, @{n = 'StartTimeUtc'; e = { if ($_.StartTime) { $_.StartTime.ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss') } else { '' } } } | Out-String -Width 300) } catch { $out += "Error: $($_.Exception.Message)" } $out += '--- Logged on users (Win32_LoggedOnUser) ---' try { $out += (Get-CimInstance Win32_LoggedOnUser -ErrorAction Stop | Select-Object -ExpandProperty Antecedent | Select-Object Domain, Name -Unique | Out-String) } catch { } $out } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'password_and_lockout_policy' -Format 'Txt' -Description 'Local account policy (net accounts)' -Script { & net.exe accounts 2>&1 | Out-String } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'user_rights_and_secpol' -Format 'Txt' -Description 'Security policy export (secedit)' -RequiresElevation -Script { $tmp = Join-Path $env:TEMP ("foton_secpol_{0}.inf" -f ([guid]::NewGuid().ToString('N'))) $null = & secedit.exe /export /cfg "$tmp" /quiet 2>&1 if (Test-Path -LiteralPath $tmp) { $content = Get-Content -LiteralPath $tmp -Raw -ErrorAction SilentlyContinue Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue $content } else { 'secedit export unavailable (requires elevation).' } } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'rdp_configuration' -Description 'Remote Desktop enablement and NLA state' -Script { $rdpUsers = 'unavailable' try { $rdpUsers = ((Get-LocalGroupMember -Group 'Remote Desktop Users' -ErrorAction Stop | Select-Object -ExpandProperty Name) -join '; ') } catch { try { $rdpUsers = ((Get-FotonLocalGroupMemberAdsi -GroupName 'Remote Desktop Users' | Select-Object -ExpandProperty Member) -join '; ') } catch { } } $ts = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' $rdpTcp = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' [PSCustomObject]@{ DenyTSConnections = (Get-FotonRegValue $ts 'fDenyTSConnections') RdpEnabled = ((Get-FotonRegValue $ts 'fDenyTSConnections') -eq 0) UserAuthentication = (Get-FotonRegValue $rdpTcp 'UserAuthentication') SecurityLayer = (Get-FotonRegValue $rdpTcp 'SecurityLayer') PortNumber = (Get-FotonRegValue $rdpTcp 'PortNumber') AllowRemoteRPC = (Get-FotonRegValue 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' 'AllowRemoteRPC') RemoteDesktopUsers = $rdpUsers ShadowSetting = (Get-FotonRegValue $rdpTcp 'Shadow') } } | Out-Null Invoke-FotonCollection -Category 'Users' -Name 'stored_credentials_and_trusts' -Format 'Txt' -Description 'Credential manager entries and domain trust state' -Script { $out = @() $out += '--- cmdkey /list ---' $out += (& cmdkey.exe /list 2>&1 | Out-String) $out += '--- nltest /domain_trusts ---' $out += (& nltest.exe /domain_trusts 2>&1 | Out-String) $out += '--- Vault directories present ---' foreach ($p in @("$env:LOCALAPPDATA\Microsoft\Vault", "$env:APPDATA\Microsoft\Credentials", "$env:LOCALAPPDATA\Microsoft\Credentials")) { if (Test-FotonPathAccessible -Path $p) { $out += "PATH: $p" $out += (Get-ChildItem -LiteralPath $p -Force -Recurse -ErrorAction SilentlyContinue | Select-Object FullName, Length, LastWriteTimeUtc | Out-String -Width 300) } } $out } | Out-Null } |