feat(aw-rus): sync deploy stack, install kit, and health checks
This commit is contained in:
@@ -749,6 +749,33 @@ function New-LaunchLock {
|
||||
return `$lockPath
|
||||
}
|
||||
|
||||
function Get-SessionMarkerToken {
|
||||
param([int]`$SessionId)
|
||||
|
||||
try {
|
||||
`$explorer = Get-Process -Name 'explorer' -ErrorAction SilentlyContinue |
|
||||
Where-Object { `$_.SessionId -eq `$SessionId } |
|
||||
Sort-Object StartTime |
|
||||
Select-Object -First 1
|
||||
if (`$explorer -and `$explorer.StartTime) {
|
||||
return `$explorer.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
try {
|
||||
`$currentProcess = Get-Process -Id `$PID -ErrorAction Stop
|
||||
if (`$currentProcess.StartTime) {
|
||||
return `$currentProcess.StartTime.ToUniversalTime().ToString('yyyyMMddTHHmmssZ')
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
return [string]`$SessionId
|
||||
}
|
||||
|
||||
function Invoke-AwJsonPost {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)][string]`$Uri,
|
||||
@@ -864,7 +891,8 @@ function Send-LogonMarkerIfNeeded {
|
||||
return
|
||||
}
|
||||
|
||||
`$markerFile = Join-Path `$markerDir ("logon-{0}-{1}.marker" -f `$env:USERNAME, `$SessionId)
|
||||
`$sessionMarkerToken = Get-SessionMarkerToken -SessionId `$SessionId
|
||||
`$markerFile = Join-Path `$markerDir ("logon-{0}-{1}-{2}.marker" -f `$env:USERNAME, `$SessionId, `$sessionMarkerToken)
|
||||
if (Test-Path -LiteralPath `$markerFile) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$ExpectedUsers = @(),
|
||||
[switch]$IncludeUnexpectedProfiles
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Get-UninstallEntries {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$NamePattern
|
||||
)
|
||||
|
||||
$paths = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
||||
)
|
||||
|
||||
return @(
|
||||
Get-ItemProperty $paths -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.PSObject.Properties.Name -contains 'DisplayName' -and
|
||||
-not [string]::IsNullOrWhiteSpace([string]$_.DisplayName) -and
|
||||
([string]$_.DisplayName -match $NamePattern)
|
||||
} |
|
||||
Select-Object DisplayName, DisplayVersion, Publisher
|
||||
)
|
||||
}
|
||||
|
||||
function Get-CryptoProToolPath {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$LeafName
|
||||
)
|
||||
|
||||
$candidate = Join-Path 'C:\Program Files\Crypto Pro\CSP' $LeafName
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-CryptoProStoreMapForCurrentUser {
|
||||
param(
|
||||
[string]$CertmgrPath
|
||||
)
|
||||
|
||||
$map = @{}
|
||||
if ([string]::IsNullOrWhiteSpace($CertmgrPath) -or -not (Test-Path -LiteralPath $CertmgrPath)) {
|
||||
return $map
|
||||
}
|
||||
|
||||
$raw = & $CertmgrPath -list -store uMy 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
return $map
|
||||
}
|
||||
|
||||
$current = @{}
|
||||
foreach ($line in @($raw | ForEach-Object { [string]$_ })) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -match '^SHA1 Thumbprint\s*:\s*(.+)$') {
|
||||
$current.thumbprint = $Matches[1].Trim().ToUpperInvariant()
|
||||
continue
|
||||
}
|
||||
if ($trimmed -match '^Embedded License\s*:\s*(.+)$') {
|
||||
$current.embeddedLicense = $Matches[1].Trim()
|
||||
continue
|
||||
}
|
||||
if ($trimmed -match '^Container\s*:\s*(.+)$') {
|
||||
$current.container = $Matches[1].Trim()
|
||||
continue
|
||||
}
|
||||
if ($trimmed -match '^\[ErrorCode:') {
|
||||
if ($current.ContainsKey('thumbprint')) {
|
||||
$map[$current.thumbprint] = [pscustomobject]@{
|
||||
embeddedLicense = if ($current.ContainsKey('embeddedLicense')) { [string]$current.embeddedLicense } else { $null }
|
||||
container = if ($current.ContainsKey('container')) { [string]$current.container } else { $null }
|
||||
}
|
||||
}
|
||||
$current = @{}
|
||||
}
|
||||
}
|
||||
|
||||
return $map
|
||||
}
|
||||
|
||||
function Test-CertificateEmbeddedLicense {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
||||
[string]$CspTestPath
|
||||
)
|
||||
|
||||
$result = [ordered]@{
|
||||
ok = $null
|
||||
status = $null
|
||||
error = $null
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($CspTestPath) -or -not (Test-Path -LiteralPath $CspTestPath)) {
|
||||
$result.error = 'csptest_missing'
|
||||
return [pscustomobject]$result
|
||||
}
|
||||
|
||||
$tempPath = Join-Path $env:TEMP ("cryptopro-audit-{0}.cer" -f ([Guid]::NewGuid().ToString('N')))
|
||||
try {
|
||||
Export-Certificate -Cert $Certificate -FilePath $tempPath -Force | Out-Null
|
||||
$raw = & $CspTestPath -certlic -check -certfile $tempPath 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$licenseLine = @($raw | Where-Object { [string]$_ -match '^License:\s*' } | Select-Object -First 1)
|
||||
if ($licenseLine.Count -gt 0) {
|
||||
$statusText = (($licenseLine[0] -replace '^License:\s*', '').Trim())
|
||||
$result.status = $statusText
|
||||
$result.ok = ($statusText -match '^Good license\b')
|
||||
}
|
||||
else {
|
||||
$result.ok = $false
|
||||
$result.error = 'license_line_missing'
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result.ok = $false
|
||||
$result.error = ('csptest_exit_{0}' -f $LASTEXITCODE)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.ok = $false
|
||||
$result.error = $_.Exception.Message
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
return [pscustomobject]$result
|
||||
}
|
||||
|
||||
function Get-ProfileMap {
|
||||
$profileMap = @{}
|
||||
foreach ($profile in @(Get-CimInstance Win32_UserProfile | Where-Object { -not $_.Special })) {
|
||||
$leaf = Split-Path -Leaf ([string]$profile.LocalPath)
|
||||
if ([string]::IsNullOrWhiteSpace($leaf)) { continue }
|
||||
$profileMap[$leaf.ToUpperInvariant()] = [pscustomobject]@{
|
||||
user = $leaf
|
||||
sid = [string]$profile.SID
|
||||
localPath = [string]$profile.LocalPath
|
||||
loaded = [bool]$profile.Loaded
|
||||
}
|
||||
}
|
||||
return $profileMap
|
||||
}
|
||||
|
||||
function Get-UserAuditRows {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$RequestedUser,
|
||||
[pscustomobject]$Profile,
|
||||
[string]$CspTestPath,
|
||||
[hashtable]$CurrentUserStoreMap,
|
||||
[bool]$ExpectedSigner
|
||||
)
|
||||
|
||||
if ($null -eq $Profile) {
|
||||
return @(
|
||||
[pscustomobject]@{
|
||||
requestedUser = $RequestedUser
|
||||
profileUser = $null
|
||||
profilePath = $null
|
||||
profileLoaded = $false
|
||||
sid = $null
|
||||
storeFilePath = $null
|
||||
thumbprint = $null
|
||||
subject = $null
|
||||
issuer = $null
|
||||
notAfterUtc = $null
|
||||
hasPrivateKey = $false
|
||||
embeddedLicenseOk = $null
|
||||
embeddedLicenseStatus = $null
|
||||
container = $null
|
||||
expectedSigner = $ExpectedSigner
|
||||
actionNeeded = 'profile_missing_or_no_login'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$certDir = Join-Path $Profile.localPath 'AppData\Roaming\Microsoft\SystemCertificates\My\Certificates'
|
||||
$files = @()
|
||||
if (Test-Path -LiteralPath $certDir) {
|
||||
$files = @(Get-ChildItem -LiteralPath $certDir -File -ErrorAction SilentlyContinue)
|
||||
}
|
||||
if ($files.Count -eq 0) {
|
||||
return @(
|
||||
[pscustomobject]@{
|
||||
requestedUser = $RequestedUser
|
||||
profileUser = $Profile.user
|
||||
profilePath = $Profile.localPath
|
||||
profileLoaded = [bool]$Profile.loaded
|
||||
sid = $Profile.sid
|
||||
storeFilePath = $certDir
|
||||
thumbprint = $null
|
||||
subject = $null
|
||||
issuer = $null
|
||||
notAfterUtc = $null
|
||||
hasPrivateKey = $false
|
||||
embeddedLicenseOk = $null
|
||||
embeddedLicenseStatus = $null
|
||||
container = $null
|
||||
expectedSigner = $ExpectedSigner
|
||||
actionNeeded = 'certificate_missing'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$rows = @()
|
||||
foreach ($file in $files) {
|
||||
try {
|
||||
$cert = Get-PfxCertificate -FilePath $file.FullName
|
||||
$thumbprint = [string]$cert.Thumbprint
|
||||
$license = Test-CertificateEmbeddedLicense -Certificate $cert -CspTestPath $CspTestPath
|
||||
$storeMeta = $null
|
||||
if ($CurrentUserStoreMap.ContainsKey($thumbprint.ToUpperInvariant())) {
|
||||
$storeMeta = $CurrentUserStoreMap[$thumbprint.ToUpperInvariant()]
|
||||
}
|
||||
|
||||
$actionNeeded = 'manual_review'
|
||||
if ($cert.NotAfter -lt (Get-Date)) {
|
||||
$actionNeeded = 'renew_certificate'
|
||||
}
|
||||
elseif (-not [bool]$cert.HasPrivateKey) {
|
||||
$actionNeeded = 'attach_private_key_or_token'
|
||||
}
|
||||
elseif ($license.ok -eq $true) {
|
||||
$actionNeeded = 'ready'
|
||||
}
|
||||
elseif ($license.ok -eq $false) {
|
||||
$actionNeeded = 'embedded_license_missing_or_invalid'
|
||||
}
|
||||
|
||||
$rows += [pscustomobject]@{
|
||||
requestedUser = $RequestedUser
|
||||
profileUser = $Profile.user
|
||||
profilePath = $Profile.localPath
|
||||
profileLoaded = [bool]$Profile.loaded
|
||||
sid = $Profile.sid
|
||||
storeFilePath = $file.FullName
|
||||
thumbprint = $thumbprint
|
||||
subject = [string]$cert.Subject
|
||||
issuer = [string]$cert.Issuer
|
||||
notAfterUtc = $cert.NotAfter.ToUniversalTime().ToString('o')
|
||||
hasPrivateKey = [bool]$cert.HasPrivateKey
|
||||
embeddedLicenseOk = $license.ok
|
||||
embeddedLicenseStatus = $license.status
|
||||
container = if ($null -ne $storeMeta) { [string]$storeMeta.container } else { $null }
|
||||
expectedSigner = $ExpectedSigner
|
||||
actionNeeded = $actionNeeded
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$rows += [pscustomobject]@{
|
||||
requestedUser = $RequestedUser
|
||||
profileUser = $Profile.user
|
||||
profilePath = $Profile.localPath
|
||||
profileLoaded = [bool]$Profile.loaded
|
||||
sid = $Profile.sid
|
||||
storeFilePath = $file.FullName
|
||||
thumbprint = $null
|
||||
subject = $null
|
||||
issuer = $null
|
||||
notAfterUtc = $null
|
||||
hasPrivateKey = $false
|
||||
embeddedLicenseOk = $null
|
||||
embeddedLicenseStatus = $null
|
||||
container = $null
|
||||
expectedSigner = $ExpectedSigner
|
||||
actionNeeded = ('certificate_parse_error: {0}' -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rows
|
||||
}
|
||||
|
||||
$cspEntries = @(Get-UninstallEntries -NamePattern 'CryptoPro|КриптоПро|Крипто')
|
||||
$cspMain = @($cspEntries | Where-Object { $_.DisplayName -match 'CSP' } | Select-Object -First 1)
|
||||
$browserPlugin = @($cspEntries | Where-Object { $_.DisplayName -match 'Browser' } | Select-Object -First 1)
|
||||
$cspTestPath = Get-CryptoProToolPath -LeafName 'csptest.exe'
|
||||
$certmgrPath = Get-CryptoProToolPath -LeafName 'certmgr.exe'
|
||||
$currentUserStoreMap = Get-CryptoProStoreMapForCurrentUser -CertmgrPath $certmgrPath
|
||||
$profileMap = Get-ProfileMap
|
||||
|
||||
$targets = New-Object 'System.Collections.Generic.List[string]'
|
||||
foreach ($expected in @($ExpectedUsers)) {
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$expected)) {
|
||||
$targets.Add([string]$expected)
|
||||
}
|
||||
}
|
||||
if ($IncludeUnexpectedProfiles.IsPresent -or $targets.Count -eq 0) {
|
||||
foreach ($profileKey in @($profileMap.Keys | Sort-Object)) {
|
||||
$targets.Add([string]$profileMap[$profileKey].user)
|
||||
}
|
||||
}
|
||||
|
||||
$seen = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$rows = @()
|
||||
foreach ($target in $targets) {
|
||||
if (-not $seen.Add($target)) { continue }
|
||||
$key = $target.ToUpperInvariant()
|
||||
$profile = if ($profileMap.ContainsKey($key)) { $profileMap[$key] } else { $null }
|
||||
$rows += Get-UserAuditRows -RequestedUser $target -Profile $profile -CspTestPath $cspTestPath -CurrentUserStoreMap $currentUserStoreMap -ExpectedSigner:([bool]($ExpectedUsers -contains $target))
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
expectedSignerCount = [int](@($ExpectedUsers | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }).Count)
|
||||
readyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'ready' }).Count)
|
||||
missingProfileCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'profile_missing_or_no_login' }).Count)
|
||||
missingCertificateCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'certificate_missing' }).Count)
|
||||
noPrivateKeyCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'attach_private_key_or_token' }).Count)
|
||||
licenseProblemCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'embedded_license_missing_or_invalid' }).Count)
|
||||
renewalNeededCount = [int](@($rows | Where-Object { $_.actionNeeded -eq 'renew_certificate' }).Count)
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
|
||||
computerName = [string]$env:COMPUTERNAME
|
||||
currentUser = [string]$env:USERNAME
|
||||
cryptopro = [ordered]@{
|
||||
cspInstalled = ($null -ne $cspMain)
|
||||
cspVersion = if ($null -ne $cspMain) { [string]$cspMain.DisplayVersion } else { $null }
|
||||
browserPluginInstalled = ($null -ne $browserPlugin)
|
||||
browserPluginVersion = if ($null -ne $browserPlugin) { [string]$browserPlugin.DisplayVersion } else { $null }
|
||||
cspTestPath = $cspTestPath
|
||||
certmgrPath = $certmgrPath
|
||||
}
|
||||
expectedUsers = @($ExpectedUsers)
|
||||
signers = @($rows)
|
||||
summary = $summary
|
||||
}
|
||||
Reference in New Issue
Block a user