Uninstall-SshKey.ps1

$ErrorActionPreference = "Stop"

# 管理员 SID,用于判断当前用户是否以管理员身份运行
$管理员Sid = "S-1-5-32-544"
$是否为管理员 = whoami /groups /fo csv |
    ConvertFrom-Csv |
    Where-Object { $_.SID -eq $管理员Sid -and $_.Attributes -match "Enabled group" }

# 解析 sshd_config 中配置的 AuthorizedKeysFile 路径为实际文件系统路径
function 解析授权密钥路径 {
    param(
        [string]$配置路径
    )

    $路径 = $配置路径.Trim().Trim('"') -replace '/', '\'
    if([string]::IsNullOrWhiteSpace($路径) -or $路径 -ieq 'none') {
        return $null
    }

    $路径 = $路径 -replace '^__PROGRAMDATA__', $env:ProgramData
    $路径 = $路径.Replace('%h', $env:USERPROFILE).Replace('%u', $env:USERNAME)

    if($路径.StartsWith('~\')) {
        return Join-Path $env:USERPROFILE $路径.Substring(2)
    }

    if([System.IO.Path]::IsPathRooted($路径)) {
        return [Environment]::ExpandEnvironmentVariables($路径)
    }

    return Join-Path $env:USERPROFILE $路径
}

# 从 sshd_config 中读取 AuthorizedKeysFile 配置(支持 Match 块)
function 获取配置的授权密钥文件 {
    $默认授权密钥 = @('.ssh/authorized_keys')
    $配置路径 = Join-Path $env:ProgramData 'ssh\sshd_config'
    if(!(Test-Path -LiteralPath $配置路径)) {
        return $默认授权密钥
    }

    $全局授权密钥文件 = $null
    $管理员授权密钥文件 = $null
    $在匹配块中 = $false
    $匹配适用管理员 = $false

    foreach($原始行 in Get-Content -LiteralPath $配置路径) {
        $行 = ($原始行 -replace '\s+#.*$', '').Trim()
        if([string]::IsNullOrWhiteSpace($行) -or $行.StartsWith('#')) {
            continue
        }

        if($行 -match '^Match\s+(.+)$') {
            $在匹配块中 = $true
            $匹配条件 = $Matches[1]
            $匹配适用管理员 = $是否为管理员 -and (
                $匹配条件 -match '(^|\s)all(\s|$)' -or
                $匹配条件 -match '(^|\s)group\s+("[^"]*administrators[^"]*"|\S*administrators\S*)'
            )
            continue
        }

        if($行 -match '^AuthorizedKeysFile\s+(.+)$') {
            $授权密钥文件列表 = $Matches[1] -split '\s+' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
            if($在匹配块中) {
                if($匹配适用管理员 -and -not $管理员授权密钥文件) {
                    $管理员授权密钥文件 = $授权密钥文件列表
                }
            } elseif(-not $全局授权密钥文件) {
                $全局授权密钥文件 = $授权密钥文件列表
            }
        }
    }

    if($管理员授权密钥文件) {
        return $管理员授权密钥文件
    }

    if($全局授权密钥文件) {
        return $全局授权密钥文件
    }

    return $默认授权密钥
}

$授权密钥文件路径 = 获取配置的授权密钥文件 |
    ForEach-Object { 解析授权密钥路径 $_ } |
    Where-Object { $_ } |
    Select-Object -First 1

if(-not $授权密钥文件路径) {
    throw '没有可用的 AuthorizedKeysFile 路径配置。'
}

# 解码传入的 Base64 编码密钥文本,提取要移除的密钥行
$编码密钥文本 = "__ENCODED_KEY_TEXT__"
$密钥文本 = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($编码密钥文本))
$要移除的密钥行 = @($密钥文本 -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })

if ($要移除的密钥行.Count -eq 0) {
    throw "没有收到任何公钥内容。"
}

if (-not (Test-Path -LiteralPath $授权密钥文件路径)) {
    # 文件不存在,没有需要移除的密钥,直接退出
    exit 0
}

# 提取要移除密钥的材质(类型+Base64,不含注释)
$要移除的密钥材质 = $要移除的密钥行 | ForEach-Object { ($_ -split '\s+')[0..1] -join ' ' }

# 读取现有密钥,只保留材质不匹配的行,写回文件
$已有密钥 = @(Get-Content -LiteralPath $授权密钥文件路径 -ErrorAction SilentlyContinue |
    Where-Object { $行 = $_; $已有材质 = ($行 -split '\s+')[0..1] -join ' '; -not ($要移除的密钥材质 -contains $已有材质) })

# 以 UTF-8(无 BOM)编码写回文件
[System.IO.File]::WriteAllLines($授权密钥文件路径, $已有密钥, [Text.UTF8Encoding]::new($false))

# 如果写入的是 administrators_authorized_keys,修正文件 ACL
if ($是否为管理员 -and ([string]::Equals($授权密钥文件路径, (Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'), [System.StringComparison]::OrdinalIgnoreCase))) {
    $系统Sid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-18")
    $管理员组Sid = New-Object System.Security.Principal.SecurityIdentifier($管理员Sid)
    $访问控制列表 = New-Object System.Security.AccessControl.FileSecurity
    $访问控制列表.SetOwner($管理员组Sid)
    $访问控制列表.SetAccessRuleProtection($true, $false)
    $访问控制列表.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($系统Sid, "FullControl", "Allow")))
    $访问控制列表.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($管理员组Sid, "FullControl", "Allow")))
    Set-Acl -LiteralPath $授权密钥文件路径 -AclObject $访问控制列表
}