Public/Add-GceSshKey.ps1
|
function Add-GceSshKey { <# .SYNOPSIS Adds an SSH public key to GCP project metadata and optionally creates a local Windows user on a VM. .DESCRIPTION Adds your SSH public key to GCP project metadata in the 'ssh-keys' metadata field so the GCE guest agent can create a local user and install the key on Windows VMs (requires enable-windows-ssh=TRUE and the google-compute-engine-ssh package on the VM). Existing project ssh-keys are preserved; the new key is appended. If the user's key already exists in project metadata, the function skips the metadata update. When -InstanceName and -Zone are provided, the function first calls gcloud compute reset-windows-password to ensure the Windows user account exists on that VM. Use -SkipResetPassword to skip this step if the user already exists. .PARAMETER Project The GCP project ID to add the SSH key to. Required. .PARAMETER UserName The username for the local Windows account. This will be both the SSH username and the local account name created by the guest agent. .PARAMETER PublicKeyPath Path to the SSH public key file. Defaults to $env:USERPROFILE\.ssh\gce_windows.pub. .PARAMETER InstanceName Optional. The GCE VM instance name to create/reset the Windows user on via gcloud compute reset-windows-password. If not provided, only the project metadata is updated (the guest agent will create the user when it syncs metadata). .PARAMETER Zone The GCE zone of the VM instance. Required when InstanceName is provided. .PARAMETER SkipResetPassword Skip the gcloud compute reset-windows-password step. Use this if the user account already exists on the VM and you only need to update the SSH key in project metadata. .PARAMETER GcloudPath Path to gcloud CLI executable. Defaults to 'gcloud'. .EXAMPLE Add-GceSshKey -Project "scs-d-sprocket" -UserName "rwood-gce" Adds the default public key to project metadata. The guest agent on Windows VMs with enable-windows-ssh=TRUE will create the user and install the key. .EXAMPLE Add-GceSshKey -Project "scs-d-sprocket" -UserName "rwood-gce" -InstanceName "usc1sprwebp01" -Zone "us-central1-a" Creates the Windows user on the VM (via reset-windows-password), then adds the SSH key to project metadata. .EXAMPLE Add-GceSshKey -Project "scs-d-sprocket" -UserName "rwood-gce" -PublicKeyPath "$env:USERPROFILE\.ssh\id_ed25519.pub" Uses a custom public key file path. .EXAMPLE Add-GceSshKey -Project "scs-d-sprocket" -UserName "rwood-gce" -InstanceName "usc1sprwebp01" -Zone "us-central1-a" -SkipResetPassword Skips password reset (user already exists) and only updates project metadata. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Project, [Parameter(Mandatory = $true)] [string]$UserName, [Parameter(Mandatory = $false)] [string]$PublicKeyPath = "$env:USERPROFILE\.ssh\gce_windows.pub", [Parameter(Mandatory = $false)] [string]$InstanceName, [Parameter(Mandatory = $false)] [string]$Zone, [Parameter(Mandatory = $false)] [switch]$SkipResetPassword, [Parameter(Mandatory = $false)] [string]$GcloudPath = 'gcloud' ) $ErrorActionPreference = "Stop" if (-not (Test-Path $PublicKeyPath)) { throw "Public key not found: $PublicKeyPath. Generate one with: ssh-keygen -t ed25519 -f `"$($PublicKeyPath -replace '\.pub$','')`" -C `"$UserName`"" } # Step 1: Optionally create/reset the Windows user on a specific VM if ($InstanceName -and -not $SkipResetPassword) { if (-not $Zone) { throw "Zone parameter is required when InstanceName is provided." } Write-Verbose "$(Get-Date): [Add-GceSshKey]: Creating/resetting Windows user [$UserName] on $InstanceName" Write-Host "Creating/resetting Windows user [$UserName] on $InstanceName..." & $GcloudPath compute reset-windows-password $InstanceName --project $Project --zone $Zone --user $UserName --quiet if ($LASTEXITCODE -ne 0) { throw "Failed to create/reset Windows user [$UserName] on $InstanceName (exit code: $LASTEXITCODE)" } } # Step 2: Read current project ssh-keys Write-Verbose "$(Get-Date): [Add-GceSshKey]: Reading current project ssh-keys from $Project" $jsonOutput = & $GcloudPath compute project-info describe --project=$Project --format=json 2>$null if ($LASTEXITCODE -ne 0) { throw "Failed to describe project $Project (exit code: $LASTEXITCODE)" } $projectInfo = $jsonOutput | ConvertFrom-Json $currentKeys = ($projectInfo.commonInstanceMetadata.items | Where-Object { $_.key -eq 'ssh-keys' }).value if (-not $currentKeys) { $currentKeys = "" } # Step 3: Build new key line in GCE format: username:key-type key-data comment $pubLine = (Get-Content $PublicKeyPath -Raw).Trim() $newLine = "$UserName`:$pubLine" # Check if this exact key line already exists if ($currentKeys -and $currentKeys -match [regex]::Escape("$UserName`:$pubLine")) { Write-Host "SSH key for [$UserName] already exists in project [$Project] metadata. Skipping update." Write-Verbose "$(Get-Date): [Add-GceSshKey]: Key already present, no metadata update needed" return } $allKeys = if ($currentKeys) { "$currentKeys`n$newLine" } else { $newLine } # Step 4: Write to temp file and update metadata (avoids shell quoting issues with multi-line values) $tempFile = [System.IO.Path]::GetTempFileName() try { Set-Content -Path $tempFile -Value $allKeys -NoNewline -Encoding UTF8 Write-Verbose "$(Get-Date): [Add-GceSshKey]: Wrote ssh-keys to temp file: $tempFile" Write-Host "Adding SSH key for [$UserName] to project [$Project] metadata..." & $GcloudPath compute project-info add-metadata --project=$Project --metadata-from-file="ssh-keys=$tempFile" if ($LASTEXITCODE -ne 0) { throw "Failed to update project metadata (exit code: $LASTEXITCODE)" } Write-Host "Done. Guest agent on Windows VMs (with enable-windows-ssh=TRUE) will create/update user [$UserName] and install the key." } finally { if (Test-Path $tempFile) { Remove-Item $tempFile -Force -ErrorAction SilentlyContinue } } } |