275 lines
12 KiB
PowerShell
275 lines
12 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Create a one-shot scheduled task that runs as a gMSA or service account to create a Rubrik service-account file.
|
|
|
|
.DESCRIPTION
|
|
- Creates a temporary PowerShell script that calls Set-RscServiceAccountFile with given parameters.
|
|
- Registers a scheduled task whose principal is either a gMSA or regular service account.
|
|
- Starts the task, waits for completion, checks LastTaskResult, then optionally cleans up.
|
|
|
|
.PARAMETER Domain
|
|
The AD domain (e.g. AD). If already providing fully-qualified account, set to empty string.
|
|
|
|
.PARAMETER AccountName
|
|
The name of the service account. For gMSA, do not include trailing $. For regular accounts, use the username.
|
|
|
|
.PARAMETER AccountType
|
|
Type of account: 'gMSA' or 'ServiceAccount'. Default: 'gMSA'
|
|
|
|
.PARAMETER Password
|
|
Password for regular service accounts. Not used for gMSA accounts. Can be SecureString or plain text.
|
|
|
|
.PARAMETER SaJsonPath
|
|
Full local path to the sa.json file that RubrikSecurityCloud module will use.
|
|
|
|
.PARAMETER OutputXmlPath
|
|
Full local path to the output xml service account file (sa-rbksql.xml).
|
|
|
|
.PARAMETER TaskName
|
|
(Optional) Scheduled task name. Default: CreateRubrikSAFile-<timestamp>
|
|
|
|
.PARAMETER KeepArtifacts
|
|
If $true, keep the temporary script and task after completion. Default $false = cleanup.
|
|
|
|
.EXAMPLE
|
|
# Using gMSA (no need to user $ at end of name)
|
|
.\createSAcreds.ps1 -Domain AD -AccountName rubrikgmsa -AccountType gMSA -SaJsonPath C:\Rubrik\scripts\sa.json -OutputXmlPath C:\Rubrik\scripts\sa-rbksql.xml
|
|
|
|
.EXAMPLE
|
|
# Using regular service account with password prompt
|
|
.\createSAcreds.ps1 -Domain AD -AccountName rbksql -AccountType ServiceAccount -Password (Read-Host -AsSecureString -Prompt "Enter SA password") -SaJsonPath C:\Rubrik\scripts\sa.json -OutputXmlPath C:\Rubrik\scripts\sa-real.xml
|
|
|
|
.EXAMPLE
|
|
# Using regular service account with plain text password
|
|
.\createSAcreds.ps1 -Domain AD -AccountName rbksql -AccountType ServiceAccount -Password "MyPassword123" -SaJsonPath C:\Rubrik\scripts\sa.json -OutputXmlPath C:\Rubrik\scripts\sa-real.xml
|
|
#>
|
|
|
|
param(
|
|
[string]$Domain,
|
|
[Parameter(Mandatory=$true)][string]$AccountName,
|
|
[ValidateSet('gMSA', 'ServiceAccount')][string]$AccountType = 'gMSA',
|
|
[object]$Password,
|
|
[Parameter(Mandatory=$true)][string]$SaJsonPath,
|
|
[Parameter(Mandatory=$true)][string]$OutputXmlPath,
|
|
[string]$TaskName = "CreateRubrikSAFile-$((Get-Date).ToString('yyyyMMdd-HHmmss'))",
|
|
[switch]$KeepArtifacts
|
|
)
|
|
|
|
try {
|
|
# ---- Parameter validation ----
|
|
if ($AccountType -eq 'ServiceAccount' -and -not $Password) {
|
|
# Prompt for password if not provided for service accounts
|
|
Write-Host "Password required for service account '$AccountName'"
|
|
$Password = Read-Host -AsSecureString -Prompt "Enter password for $AccountName"
|
|
}
|
|
|
|
if ($AccountType -eq 'gMSA' -and $Password) {
|
|
Write-Warning "Password parameter ignored for gMSA accounts"
|
|
}
|
|
|
|
# ---- Basic validation ----
|
|
if (-not (Test-Path -Path $SaJsonPath)) { throw "SA JSON not found at: $SaJsonPath" }
|
|
$tempDir = Join-Path -Path $env:TEMP -ChildPath "CreateRubrikSAFile_$([guid]::NewGuid().ToString().Substring(0,8))"
|
|
New-Item -Path $tempDir -ItemType Directory -Force | Out-Null
|
|
|
|
$oneShotScript = Join-Path $tempDir "Create-SA-File.ps1"
|
|
$logFile = Join-Path $tempDir "Create-SA-File.log"
|
|
|
|
# ---- Create the one-shot script that will run under the service account ----
|
|
$oneShotContent = @"
|
|
# One-shot script created by createSAcreds.ps1
|
|
# Runs RubrikSecurityCloud command to create service-account file
|
|
|
|
# Start transcript for detailed logging
|
|
Start-Transcript -Path `"$logFile`" -Append
|
|
|
|
Write-Output "Script started at: `$(Get-Date)"
|
|
Write-Output "Running as user: `$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
|
Write-Output "PowerShell version: `$(`$PSVersionTable.PSVersion)"
|
|
|
|
Try {
|
|
Write-Output "Attempting to import RubrikSecurityCloud module..."
|
|
Import-Module RubrikSecurityCloud -ErrorAction Stop
|
|
Write-Output "Successfully imported RubrikSecurityCloud module"
|
|
} Catch {
|
|
Write-Error "Failed to import RubrikSecurityCloud module: `$(`$_.Exception.Message)"
|
|
Write-Error "Full exception: `$(`$_.Exception | Format-List * | Out-String)"
|
|
Stop-Transcript
|
|
Exit 2
|
|
}
|
|
|
|
Try {
|
|
Write-Output "Checking input file: $SaJsonPath"
|
|
# Ensure the input file exists
|
|
if (-not (Test-Path -Path `"$SaJsonPath`")) {
|
|
Write-Error "Input SA JSON not found: $SaJsonPath"
|
|
Stop-Transcript
|
|
Exit 3
|
|
}
|
|
Write-Output "Input file found, size: `$((Get-Item `"$SaJsonPath`").Length) bytes"
|
|
|
|
Write-Output "Calling Set-RscServiceAccountFile..."
|
|
Write-Output " Input: $SaJsonPath"
|
|
Write-Output " Output: $OutputXmlPath"
|
|
|
|
Set-RscServiceAccountFile `"$SaJsonPath`" -OutputFilePath `"$OutputXmlPath`" -Verbose
|
|
|
|
Write-Output "Set-RscServiceAccountFile completed"
|
|
|
|
if (Test-Path -Path `"$OutputXmlPath`") {
|
|
Write-Output "Service account XML created successfully: $OutputXmlPath"
|
|
Write-Output "Output file size: `$((Get-Item `"$OutputXmlPath`").Length) bytes"
|
|
Stop-Transcript
|
|
Exit 0
|
|
} else {
|
|
Write-Error "Set-RscServiceAccountFile completed but output file not found: $OutputXmlPath"
|
|
Write-Error "Checking parent directory: `$(Split-Path `"$OutputXmlPath`")"
|
|
if (Test-Path (Split-Path `"$OutputXmlPath`")) {
|
|
Write-Output "Parent directory exists, listing contents:"
|
|
Get-ChildItem (Split-Path `"$OutputXmlPath`") | ForEach-Object { Write-Output " `$(`$_.Name)" }
|
|
} else {
|
|
Write-Error "Parent directory does not exist: `$(Split-Path `"$OutputXmlPath`")"
|
|
}
|
|
Stop-Transcript
|
|
Exit 4
|
|
}
|
|
} Catch {
|
|
Write-Error "Error creating Rubrik service-account file: `$(`$_.Exception.Message)"
|
|
Write-Error "Full exception: `$(`$_.Exception | Format-List * | Out-String)"
|
|
Write-Error "Stack trace: `$(`$_.ScriptStackTrace)"
|
|
Stop-Transcript
|
|
Exit 5
|
|
}
|
|
"@
|
|
|
|
# Replace placeholders (so we don't have to escape too much)
|
|
$oneShotContent = $oneShotContent -replace '\$SaJsonPath', [Regex]::Escape($SaJsonPath)
|
|
$oneShotContent = $oneShotContent -replace '\$OutputXmlPath', [Regex]::Escape($OutputXmlPath)
|
|
$oneShotContent = $oneShotContent -replace '\$logFile', [Regex]::Escape($logFile)
|
|
|
|
Set-Content -Path $oneShotScript -Value $oneShotContent -Encoding UTF8
|
|
|
|
# Make sure executable by scheduled task
|
|
icacls $oneShotScript /grant "BUILTIN\Administrators:(R,W)" | Out-Null
|
|
|
|
# ---- Build Scheduled Task objects ----
|
|
# Construct the UserId based on account type
|
|
if ($AccountType -eq 'gMSA') {
|
|
$userId = if ([string]::IsNullOrWhiteSpace($Domain)) { "$AccountName`$" } else { "$Domain\$AccountName`$" }
|
|
$logonType = 'Password' # needed even though gMSA uses AD for auth
|
|
} else {
|
|
$userId = if ([string]::IsNullOrWhiteSpace($Domain)) { $AccountName } else { "$Domain\$AccountName" }
|
|
$logonType = 'Password'
|
|
}
|
|
|
|
# Action: run PowerShell to execute the one-shot script with output redirection
|
|
$psArgs = "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$oneShotScript`" *>&1 | Tee-Object -FilePath `"$logFile`" -Append"
|
|
$action = New-ScheduledTaskAction -Execute (Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe') -Argument $psArgs
|
|
|
|
# Trigger: once, a short time in the future (1 minute from now)
|
|
$startTime = (Get-Date).AddMinutes(1)
|
|
$trigger = New-ScheduledTaskTrigger -Once -At $startTime
|
|
|
|
# Principal: service account or gMSA
|
|
$principal = New-ScheduledTaskPrincipal -UserId $userId -LogonType $logonType -RunLevel Highest
|
|
|
|
# Settings: one-shot, don't persist run as logged on user UI
|
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 1)
|
|
|
|
$task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settings
|
|
|
|
# ---- Register the scheduled task ----
|
|
if ($AccountType -eq 'gMSA') {
|
|
# For gMSA, register without password (AD will handle authentication)
|
|
Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force
|
|
Write-Host "Registered scheduled task '$TaskName' to run as gMSA $userId at $startTime."
|
|
} else {
|
|
# For regular service accounts, register with password
|
|
$securePassword = if ($Password -is [SecureString]) { $Password } else { ConvertTo-SecureString $Password -AsPlainText -Force }
|
|
Register-ScheduledTask -TaskName $TaskName -InputObject $task -User $userId -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword))) -Force
|
|
Write-Host "Registered scheduled task '$TaskName' to run as service account $userId at $startTime."
|
|
}
|
|
|
|
# Optionally start immediately (Start-ScheduledTask will ignore trigger time and try to run it)
|
|
Start-ScheduledTask -TaskName $TaskName
|
|
Write-Host "Started task. Waiting for completion..."
|
|
|
|
# ---- Wait for completion and inspect result ----
|
|
$maxWaitSeconds = 600
|
|
$pollInterval = 3
|
|
$elapsed = 0
|
|
$lastResult = $null
|
|
$taskCompleted = $false
|
|
|
|
while ($true) {
|
|
Start-Sleep -Seconds $pollInterval
|
|
$elapsed += $pollInterval
|
|
|
|
$info = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue
|
|
if ($null -eq $info) {
|
|
Write-Warning "Could not query task info yet."
|
|
} else {
|
|
# LastTaskResult returns Win32 error code; 0 = success
|
|
$lastResult = $info.LastTaskResult
|
|
$state = $info.State
|
|
Write-Host "Task state: '$state'; LastResult: $lastResult"
|
|
|
|
# Task is complete if:
|
|
# 1. State is Ready/Disabled/Unknown AND we have a valid LastResult
|
|
# 2. OR if LastResult changed from 267009 (SCHED_S_TASK_RUNNING) to something else
|
|
if (($state -eq 'Ready' -or $state -eq 'Disabled' -or $state -eq 'Unknown' -or [string]::IsNullOrEmpty($state)) -and
|
|
($lastResult -ne $null -and $lastResult -ne 267009)) {
|
|
$taskCompleted = $true
|
|
break
|
|
}
|
|
|
|
if ($state -eq 'Running') {
|
|
Write-Host "Task still running..."
|
|
}
|
|
}
|
|
|
|
if ($elapsed -ge $maxWaitSeconds) {
|
|
throw "Timed out waiting for scheduled task to finish (waited $maxWaitSeconds seconds)."
|
|
}
|
|
}
|
|
|
|
Write-Host "Task completed after $elapsed seconds."
|
|
|
|
# ---- Check exit status and output ----
|
|
if ($lastResult -eq 0) {
|
|
Write-Host "Task finished successfully (LastTaskResult=0)."
|
|
if (Test-Path -Path $OutputXmlPath) {
|
|
Write-Host "Found output XML: $OutputXmlPath"
|
|
} else {
|
|
Write-Warning "Task indicated success but output file not found at $OutputXmlPath"
|
|
}
|
|
} else {
|
|
Write-Host "Scheduled task finished with non-zero LastTaskResult: $lastResult"
|
|
|
|
# Display log file contents for troubleshooting
|
|
if (Test-Path -Path $logFile) {
|
|
Write-Host "`n--- Log file contents ($logFile) ---"
|
|
Get-Content -Path $logFile | ForEach-Object { Write-Host $_ }
|
|
Write-Host "--- End of log file ---`n"
|
|
} else {
|
|
Write-Warning "Log file not found at: $logFile"
|
|
}
|
|
|
|
throw "Scheduled task finished with non-zero LastTaskResult: $lastResult."
|
|
}
|
|
|
|
# ---- Cleanup ----
|
|
if (-not $KeepArtifacts) {
|
|
Write-Host "Cleaning up task and temporary files..."
|
|
try { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue } catch {}
|
|
try { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } catch {}
|
|
Write-Host "Cleanup complete."
|
|
} else {
|
|
Write-Host "Kept task '$TaskName' and temporary script at: $oneShotScript"
|
|
}
|
|
|
|
} catch {
|
|
Write-Error "ERROR: $($_.Exception.Message)"
|
|
throw
|
|
}
|