diff --git a/createSAcreds.ps1 b/createSAcreds.ps1 index ab9654e..81ce4aa 100644 --- a/createSAcreds.ps1 +++ b/createSAcreds.ps1 @@ -32,8 +32,8 @@ 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 + # Using gMSA + .\createSAcreds.ps1 -Domain AD -AccountName rubrikgmsa -AccountType gMSA -SaJsonPath C:\temp\sa.json -OutputXmlPath C:\temp\sa-rbksql.xml .EXAMPLE # Using regular service account with password prompt @@ -76,12 +76,16 @@ try { $logFile = Join-Path $tempDir "Create-SA-File.log" # ---- Create the one-shot script that will run under the service account ---- + $escapedSaJsonPath = $SaJsonPath -replace '\\', '\\' + $escapedOutputXmlPath = $OutputXmlPath -replace '\\', '\\' + $escapedLogFile = $logFile -replace '\\', '\\' + $oneShotContent = @" -# One-shot script created by createSAcreds.ps1 +# One-shot script created by create-and-run-one-shot-via-gMSA.ps1 # Runs RubrikSecurityCloud command to create service-account file # Start transcript for detailed logging -Start-Transcript -Path `"$logFile`" -Append +Start-Transcript -Path "$escapedLogFile" -Append Write-Output "Script started at: `$(Get-Date)" Write-Output "Running as user: `$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" @@ -99,42 +103,42 @@ Try { } Try { - Write-Output "Checking input file: $SaJsonPath" + Write-Output "Checking input file: $escapedSaJsonPath" # Ensure the input file exists - if (-not (Test-Path -Path `"$SaJsonPath`")) { - Write-Error "Input SA JSON not found: $SaJsonPath" + if (-not (Test-Path -Path "$escapedSaJsonPath")) { + Write-Error "Input SA JSON not found: $escapedSaJsonPath" Stop-Transcript Exit 3 } - Write-Output "Input file found, size: `$((Get-Item `"$SaJsonPath`").Length) bytes" + Write-Output "Input file found, size: `$((Get-Item "$escapedSaJsonPath").Length) bytes" Write-Output "Calling Set-RscServiceAccountFile..." - Write-Output " Input: $SaJsonPath" - Write-Output " Output: $OutputXmlPath" + Write-Output " Input: $escapedSaJsonPath" + Write-Output " Output: $escapedOutputXmlPath" - Set-RscServiceAccountFile `"$SaJsonPath`" -OutputFilePath `"$OutputXmlPath`" -Verbose + Set-RscServiceAccountFile "$escapedSaJsonPath" -OutputFilePath "$escapedOutputXmlPath" -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" + if (Test-Path -Path "$escapedOutputXmlPath") { + Write-Output "Service account XML created successfully: $escapedOutputXmlPath" + Write-Output "Output file size: `$((Get-Item "$escapedOutputXmlPath").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-Error "Set-RscServiceAccountFile completed but output file not found: $escapedOutputXmlPath" + Write-Error "Checking parent directory: `$(Split-Path "$escapedOutputXmlPath")" + if (Test-Path (Split-Path "$escapedOutputXmlPath")) { Write-Output "Parent directory exists, listing contents:" - Get-ChildItem (Split-Path `"$OutputXmlPath`") | ForEach-Object { Write-Output " `$(`$_.Name)" } + Get-ChildItem (Split-Path "$escapedOutputXmlPath") | ForEach-Object { Write-Output " `$(`$_.Name)" } } else { - Write-Error "Parent directory does not exist: `$(Split-Path `"$OutputXmlPath`")" + Write-Error "Parent directory does not exist: `$(Split-Path "$escapedOutputXmlPath")" } Stop-Transcript Exit 4 } } Catch { - Write-Error "Error creating Rubrik service-account file: `$(`$_.Exception.Message)" + Write-Error "Error creating RBK service-account file: `$(`$_.Exception.Message)" Write-Error "Full exception: `$(`$_.Exception | Format-List * | Out-String)" Write-Error "Stack trace: `$(`$_.ScriptStackTrace)" Stop-Transcript @@ -142,29 +146,37 @@ Try { } "@ - # 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 + try { + icacls $oneShotScript /grant "BUILTIN\Administrators:(R,W)" | Out-Null + } catch { + Write-Warning "Could not set permissions on script file: $($_.Exception.Message)" + } # ---- 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 + if ([string]::IsNullOrWhiteSpace($Domain)) { + $userId = "$AccountName`$" + } else { + $userId = "$Domain\$AccountName`$" + } + $logonType = 'Password' # For gMSA, use Password logon type } else { - $userId = if ([string]::IsNullOrWhiteSpace($Domain)) { $AccountName } else { "$Domain\$AccountName" } - $logonType = 'Password' + if ([string]::IsNullOrWhiteSpace($Domain)) { + $userId = $AccountName + } else { + $userId = "$Domain\$AccountName" + } + $logonType = 'Password' # For regular service accounts, use Password logon type } # 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 + $psExePath = Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe' + $psArgs = "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$oneShotScript`"" + $action = New-ScheduledTaskAction -Execute $psExePath -Argument $psArgs # Trigger: once, a short time in the future (1 minute from now) $startTime = (Get-Date).AddMinutes(1) @@ -185,8 +197,13 @@ Try { 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 + if ($Password -is [SecureString]) { + $securePassword = $Password + } else { + $securePassword = ConvertTo-SecureString $Password -AsPlainText -Force + } + $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)) + Register-ScheduledTask -TaskName $TaskName -InputObject $task -User $userId -Password $plainPassword -Force Write-Host "Registered scheduled task '$TaskName' to run as service account $userId at $startTime." } @@ -205,27 +222,31 @@ Try { 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..." + try { + $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 + ($null -ne $lastResult -and $lastResult -ne 267009)) { + $taskCompleted = $true + break + } + + if ($state -eq 'Running') { + Write-Host "Task still running..." + } } + } catch { + Write-Warning "Error querying task status: $($_.Exception.Message)" } if ($elapsed -ge $maxWaitSeconds) { @@ -255,7 +276,7 @@ Try { Write-Warning "Log file not found at: $logFile" } - throw "Scheduled task finished with non-zero LastTaskResult: $lastResult." + throw "Scheduled task finished with non-zero LastTaskResult: $lastResult. Check Event Viewer > Applications and Services Logs > Microsoft > Windows > TaskScheduler for details, or review the log output above." } # ---- Cleanup ---- @@ -270,5 +291,11 @@ Try { } catch { Write-Error "ERROR: $($_.Exception.Message)" + if (-not $KeepArtifacts -and $TaskName) { + try { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue } catch {} + } + if (-not $KeepArtifacts -and $tempDir -and (Test-Path $tempDir)) { + try { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } catch {} + } throw }