Anka Software Updates (#4256)
* Anka Software Updates * allow to set up video contoller
This commit is contained in:
@@ -0,0 +1,218 @@
|
|||||||
|
function Push-AnkaTemplateToRegistry {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $RegistryUrl,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TagVersion,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplateName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka registry -a $RegistryUrl push -t $TagVersion $TemplateName"
|
||||||
|
Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AnkaVM {
|
||||||
|
param(
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka --machine-readable list"
|
||||||
|
if (-not [string]::IsNullOrEmpty($VMName)) {
|
||||||
|
$command = "anka --machine-readable show $VMName"
|
||||||
|
}
|
||||||
|
Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object body
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AnkaVMStatus {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka --machine-readable list $VMName"
|
||||||
|
Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object { $_.body.status }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AnkaVMIPAddress {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
Get-AnkaVM -VMName $VMName | Foreach-Object ip
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-AnkaCommand {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $Command
|
||||||
|
)
|
||||||
|
|
||||||
|
$result = bash -c "$Command 2>&1" | Out-String
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "There is an error during command execution:`n$result"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$result
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-AnkaVMTemplate {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $InstallerPath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplateName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplateUsername,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplatePassword,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[int] $CPUCount,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[int] $RamSizeGb,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[int] $DiskSizeGb
|
||||||
|
)
|
||||||
|
|
||||||
|
$env:ANKA_DEFAULT_USER = $TemplateUsername
|
||||||
|
$env:ANKA_DEFAULT_PASSWD = $TemplatePassword
|
||||||
|
$env:ANKA_CREATE_SUSPEND = 0
|
||||||
|
$command = "anka create --cpu-count '$CPUCount' --ram-size '${RamSizeGb}G' --disk-size '${DiskSizeGb}G' --app '$InstallerPath' $TemplateName"
|
||||||
|
Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-AnkaVM {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka delete $VMName --yes"
|
||||||
|
$isTemplateExists = Get-AnkaVM | Where-Object name -eq $VMName
|
||||||
|
if ($isTemplateExists) {
|
||||||
|
$null = Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-AnkaVMVideoController {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $ShortMacOSVersion,
|
||||||
|
|
||||||
|
[ValidateSet("fbuf", "pg")]
|
||||||
|
[string] $Controller = "pg"
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka modify $VMName set display -c $Controller"
|
||||||
|
# Apple Metal is available starting from Big Sur
|
||||||
|
if (-not $ShortMacOSVersion.StartsWith("10.")) {
|
||||||
|
$null = Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-AnkaVMDisplayResolution {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $DisplayResolution
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka modify $VMName set display -r $DisplayResolution"
|
||||||
|
$null = Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-AnkaVM {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka start $VMName"
|
||||||
|
$vmStatus = Get-AnkaVMStatus -VMName $VMName
|
||||||
|
if ($vmStatus -eq "stopped") {
|
||||||
|
$null = Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-AnkaVM {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "anka stop $VMName"
|
||||||
|
$vmStatus = Get-AnkaVMStatus -VMName $VMName
|
||||||
|
if ($vmStatus -eq "running") {
|
||||||
|
$null = Invoke-AnkaCommand -Command $command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-AnkaVMIPAddress {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName,
|
||||||
|
|
||||||
|
[int] $RetryCount = 20,
|
||||||
|
[int] $Seconds = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
$condition = { Get-AnkaVMIPAddress -VMName $VMName }
|
||||||
|
$null = Invoke-WithRetry -BreakCondition $condition -RetryCount $RetryCount -Seconds $Seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-AnkaVMSSHService {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName,
|
||||||
|
|
||||||
|
[int] $RetryCount = 20,
|
||||||
|
[int] $Seconds = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
Start-Sleep -Seconds $Seconds
|
||||||
|
Write-Host "`t[*] Waiting for '$VMName' VM to get an IP address"
|
||||||
|
Wait-AnkaVMIPAddress -VMName $VMName -RetryCount $RetryCount -Seconds $Seconds
|
||||||
|
|
||||||
|
$ipAddress = Get-AnkaVMIPAddress -VMName $VMName
|
||||||
|
Write-Host "`t[*] The '$ipAddress' IP address for '$VMName' VM"
|
||||||
|
|
||||||
|
Write-Host "`t[*] Checking if SSH on a port is open"
|
||||||
|
$isSSHPortOpen = Test-SSHPort -IPAddress $ipAddress
|
||||||
|
if (-not $isSSHPortOpen) {
|
||||||
|
Write-Host "`t[x] SSH port is closed"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[version] $MacOSVersion,
|
||||||
|
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplateUsername,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplatePassword,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $RegistryUrl,
|
||||||
|
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $TemplateName,
|
||||||
|
|
||||||
|
[bool] $DownloadLatestVersion = $true,
|
||||||
|
[bool] $BetaSearch = $false,
|
||||||
|
[bool] $InstallSoftwareUpdate = $true,
|
||||||
|
[bool] $EnableAutoLogon = $true,
|
||||||
|
[int] $CPUCount = 6,
|
||||||
|
[int] $RamSizeGb = 7,
|
||||||
|
[int] $DiskSizeGb = 300,
|
||||||
|
[string] $DisplayResolution = "1920x1080"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$WarningPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# Import helper modules
|
||||||
|
Import-Module "$PSScriptRoot/Anka.Helpers.psm1"
|
||||||
|
Import-Module "$PSScriptRoot/Service.Helpers.psm1"
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
function Invoke-EnableAutoLogon {
|
||||||
|
if (-not $EnableAutoLogon) {
|
||||||
|
Write-Host "`t[*] Skip configuring AutoLogon"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
|
||||||
|
|
||||||
|
Write-Host "`t[*] Enable AutoLogon"
|
||||||
|
Enable-AutoLogon -HostName $ipAddress -UserName $TemplateUsername -Password $TemplatePassword
|
||||||
|
|
||||||
|
Write-Host "`t[*] Reboot '$TemplateName' VM to enable AutoLogon"
|
||||||
|
Restart-VMSSH -HostName $ipAddress | Show-StringWithFormat
|
||||||
|
|
||||||
|
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
|
||||||
|
|
||||||
|
Write-Host "`t[*] Checking if AutoLogon is enabled"
|
||||||
|
Test-AutoLogon -VMName $TemplateName -UserName $TemplateUsername
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-SoftwareUpdate {
|
||||||
|
if (-not $InstallSoftwareUpdate) {
|
||||||
|
Write-Host "`t[*] Skip installing software updates"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
|
||||||
|
|
||||||
|
# Install Software Updates
|
||||||
|
# Security updates may not be able to install(hang, freeze) when AutoLogon is turned off
|
||||||
|
Write-Host "`t[*] Finding available software"
|
||||||
|
$newUpdates = Get-SoftwareUpdate -HostName $ipAddress
|
||||||
|
|
||||||
|
if (-not $newUpdates) {
|
||||||
|
Write-Host "`t[*] No Updates Available"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`t[*] Fetching Software Updates ready to install on '$TemplateName' VM:"
|
||||||
|
Show-StringWithFormat $newUpdates
|
||||||
|
Write-Host "`t[*] Installing Software Updates on 'test_vm' VM:"
|
||||||
|
Install-SoftwareUpdate -HostName $ipAddress | Show-StringWithFormat
|
||||||
|
|
||||||
|
# Check if Action: restart
|
||||||
|
if ($newUpdates.Contains("Action: restart")) {
|
||||||
|
Write-Host "`t[*] Sleep 60 seconds before the software updates have been installed"
|
||||||
|
Start-Sleep -Seconds 60
|
||||||
|
|
||||||
|
Write-Host "`t[*] Waiting for loginwindow process"
|
||||||
|
Wait-LoginWindow -HostName $ipAddress | Show-StringWithFormat
|
||||||
|
|
||||||
|
# Re-enable AutoLogon after installing a new security software update
|
||||||
|
Invoke-EnableAutoLogon
|
||||||
|
|
||||||
|
# Check software updates have been installed
|
||||||
|
$updates = Get-SoftwareUpdate -HostName $ipAddress
|
||||||
|
if ($updates.Contains("Action: restart")) {
|
||||||
|
Write-Host "`t[x] Software updates failed to install: $updates"
|
||||||
|
Show-StringWithFormat $updates
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`t[*] Show the install history:"
|
||||||
|
$hUpdates = Get-SoftwareUpdateHistory -HostName $ipAddress
|
||||||
|
Show-StringWithFormat $hUpdates
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-UpdateSettings {
|
||||||
|
$isConfRequired = $InstallSoftwareUpdate -or $EnableAutoLogon
|
||||||
|
if (-not $isConfRequired) {
|
||||||
|
Write-Host "`t[*] Skip additional configuration"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`t[*] Starting '$TemplateName' VM"
|
||||||
|
Start-AnkaVM -VMName $TemplateName
|
||||||
|
|
||||||
|
Write-Host "`t[*] Waiting for SSH service on '$TemplateName' VM"
|
||||||
|
Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30
|
||||||
|
|
||||||
|
# Configure AutoLogon
|
||||||
|
Invoke-EnableAutoLogon
|
||||||
|
|
||||||
|
# Install software updates
|
||||||
|
Invoke-SoftwareUpdate
|
||||||
|
|
||||||
|
Write-Host "`t[*] Stopping '$TemplateName' VM"
|
||||||
|
Stop-AnkaVM -VMName $TemplateName
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-VMStopped {
|
||||||
|
$vmStatus = Get-AnkaVMStatus -VMName $TemplateName
|
||||||
|
if ($vmStatus -ne "stopped") {
|
||||||
|
Write-Host "`t[x] VM '$TemplateName' state is not stopped. The current state is '$vmStatus'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Password is passed as env-var "SSHPASS"
|
||||||
|
$env:SSHUSER = $TemplateUsername
|
||||||
|
$env:SSHPASS = $TemplatePassword
|
||||||
|
|
||||||
|
Write-Host "`n[#1] Download macOS application installer:"
|
||||||
|
$macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
|
||||||
|
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
||||||
|
if ([string]::IsNullOrEmpty($TemplateName)) {
|
||||||
|
$TemplateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[#2] Create a VM template:"
|
||||||
|
Write-Host "`t[*] Deleting existed template with name '$TemplateName' before creating a new one"
|
||||||
|
Remove-AnkaVM -VMName $TemplateName
|
||||||
|
|
||||||
|
Write-Host "`t[*] Creating Anka VM template with name '$TemplateName' and '$TemplateUsername' user"
|
||||||
|
Write-Host "`t[*] CPU Count: $CPUCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $macOSInstaller, TemplateName: $TemplateName"
|
||||||
|
New-AnkaVMTemplate -InstallerPath $macOSInstaller `
|
||||||
|
-TemplateName $TemplateName `
|
||||||
|
-TemplateUsername $TemplateUsername `
|
||||||
|
-TemplatePassword $TemplatePassword `
|
||||||
|
-CPUCount $CPUCount `
|
||||||
|
-RamSizeGb $RamSizeGb `
|
||||||
|
-DiskSizeGb $DiskSizeGb | Show-StringWithFormat
|
||||||
|
|
||||||
|
Write-Host "`n[#3] Configure AutoLogon and/or install software updates:"
|
||||||
|
Invoke-UpdateSettings
|
||||||
|
|
||||||
|
Write-Host "`n[#4] Finalization '$TemplateName' configuration and push to the registry:"
|
||||||
|
Write-Host "`t[*] The '$TemplateName' VM status is stopped"
|
||||||
|
Test-VMStopped
|
||||||
|
|
||||||
|
# Configure graphics settings
|
||||||
|
Write-Host "`t[*] Enabling Graphics Acceleration with Apple Metal for '$TemplateName' VM"
|
||||||
|
Set-AnkaVMVideoController -VMName $TemplateName -ShortMacOSVersion $ShortMacOSVersion
|
||||||
|
|
||||||
|
Write-Host "`t[*] Setting screen resolution to $DisplayResolution for $TemplateName"
|
||||||
|
Set-AnkaVMDisplayResolution -VMName $TemplateName -DisplayResolution $DisplayResolution
|
||||||
|
|
||||||
|
# Push a VM template (and tag) to the Cloud
|
||||||
|
Write-Host "`t[*] Pushing '$TemplateName' image with '$ShortMacOSVersion' tag to the '$RegistryUrl' registry..."
|
||||||
|
Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagVersion $shortMacOSVersion -TemplateName $TemplateName
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
function Enable-AutoLogon {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $UserName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $Password
|
||||||
|
)
|
||||||
|
|
||||||
|
$url = "https://raw.githubusercontent.com/actions/virtual-environments/main/images/macos/provision/bootstrap-provisioner/kcpassword.py"
|
||||||
|
$script = Invoke-RestMethod -Uri $url
|
||||||
|
$base64 = [Convert]::ToBase64String($script.ToCharArray())
|
||||||
|
$kcpassword = "echo $base64 | base64 --decode > ~/kcpassword;sudo python ./kcpassword '${Password}';rm ./kcpassword"
|
||||||
|
$loginwindow = "sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser '${UserName}';sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false"
|
||||||
|
$command = "${kcpassword};$loginwindow"
|
||||||
|
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AvailableVersions {
|
||||||
|
param (
|
||||||
|
[bool] $IsBeta = $false
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($IsBeta) {
|
||||||
|
$searchPostfix = " beta"
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = { /usr/sbin/softwareupdate --list-full-installers | grep "macOS" }
|
||||||
|
$condition = { $LASTEXITCODE -eq 0 }
|
||||||
|
$softwareUpdates = Invoke-WithRetry -Command $command -BreakCondition $condition | Where-Object { $_.Contains("Title: macOS") -and $_ -match $searchPostfix }
|
||||||
|
$allVersions = $softwareUpdates -replace "(\* )?(Title|Version|Size):" | ConvertFrom-Csv -Header OSName, OSVersion | Select-Object OSName, OSVersion -Unique
|
||||||
|
|
||||||
|
$allVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-MacOSInstaller {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[version] $MacOSVersion,
|
||||||
|
|
||||||
|
[bool] $DownloadLatestVersion = $false,
|
||||||
|
[bool] $BetaSearch = $false
|
||||||
|
)
|
||||||
|
|
||||||
|
# Enroll machine to DeveloperSeed if we need beta and unenroll otherwise
|
||||||
|
$seedutil = "/System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil"
|
||||||
|
if ($BetaSearch) {
|
||||||
|
Write-Host "`t[*] Beta Version requested. Enrolling machine to DeveloperSeed"
|
||||||
|
sudo $seedutil enroll DeveloperSeed | Out-Null
|
||||||
|
} else {
|
||||||
|
Write-Host "`t[*] Reseting the seed before requesting stable versions"
|
||||||
|
sudo $seedutil unenroll | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate there is no softwareupdate at the moment
|
||||||
|
Test-SoftwareUpdate
|
||||||
|
|
||||||
|
# Validate availability OSVersion
|
||||||
|
Write-Host "`t[*] Finding available full installers"
|
||||||
|
$availableVersions = Get-AvailableVersions -IsBeta $BetaSearch
|
||||||
|
if ($DownloadLatestVersion) {
|
||||||
|
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
||||||
|
$filterSearch = "${shortMacOSVersion}."
|
||||||
|
$filteredVersions = $availableVersions.Where{ $_.OSVersion.StartsWith($filterSearch) }
|
||||||
|
if (-not $filteredVersions) {
|
||||||
|
Write-Host "`t[x] Failed to find any macOS versions using '$filterSearch' search condition"
|
||||||
|
Show-StringWithFormat $availableVersions
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Show-StringWithFormat $filteredVersions
|
||||||
|
$osVersions = $filteredVersions.OSVersion | Sort-Object {[version]$_}
|
||||||
|
$MacOSVersion = $osVersions | Select-Object -Last 1
|
||||||
|
Write-Host "`t[*] The 'DownloadLatestVersion' flag is set. Latest macOS version is '$MacOSVersion' now"
|
||||||
|
}
|
||||||
|
|
||||||
|
$macOSName = $availableVersions.Where{ $MacOSVersion -eq $_.OSVersion }.OSName
|
||||||
|
if (-not $macOSName) {
|
||||||
|
Write-Host "`t[x] Requested macOS '$MacOSVersion' version not found in the list of available installers. Available versions are:`n$($availableVersions.OSVersion)"
|
||||||
|
Write-Host "`t[x] Make sure to pass '-BetaSearch `$true' if you need a beta version installer"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$installerPathPattern = "/Applications/Install*${macOSName}.app"
|
||||||
|
if (Test-Path $installerPathPattern) {
|
||||||
|
$previousInstallerPath = Get-Item -Path $installerPathPattern
|
||||||
|
Write-Host "`t[*] Removing '$previousInstallerPath' installation app before downloading the new one"
|
||||||
|
sudo rm -rf "$previousInstallerPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Download macOS installer
|
||||||
|
Write-Host "`t[*] Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
|
||||||
|
$result = Invoke-WithRetry { /usr/sbin/softwareupdate --fetch-full-installer --full-installer-version $MacOSVersion } {$LASTEXITCODE -eq 0} | Out-String
|
||||||
|
if (-not $result.Contains("Install finished successfully")) {
|
||||||
|
Write-Host "`t[x] Failed to fetch $MacOSVersion macOS `n$result"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$installerPath = (Get-Item -Path $installerPathPattern).FullName
|
||||||
|
Write-Host "`t[*] Installer successfully downloaded to '$installerPath'"
|
||||||
|
|
||||||
|
$installerPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ShortMacOSVersion {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[version] $MacOSVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
# Take Major.Minor version for macOS 10 (10.14 or 10.15) and Major for all further versions
|
||||||
|
$MacOSVersion.Major -eq 10 ? $MacOSVersion.ToString(2) : $MacOSVersion.ToString(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SoftwareUpdate {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "/usr/sbin/softwareupdate --list"
|
||||||
|
$result = Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||||
|
$result | Where-Object { $_ -match "(Label|Title):" } | Out-String
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SoftwareUpdateHistory {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "/usr/sbin/softwareupdate --history"
|
||||||
|
Invoke-SSHPassCommand -HostName $HostName -Command $command | Out-String
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-SoftwareUpdate {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "sudo /usr/sbin/softwareupdate --all --install --restart --verbose"
|
||||||
|
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-SSHPassCommand {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $Command,
|
||||||
|
|
||||||
|
[int] $ConnectTimeout = 10,
|
||||||
|
[int] $ConnectionAttempts = 10,
|
||||||
|
[int] $ServerAliveInterval = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
$sshArg = @(
|
||||||
|
"sshpass"
|
||||||
|
"-e"
|
||||||
|
"ssh"
|
||||||
|
"-o UserKnownHostsFile=/dev/null"
|
||||||
|
"-o StrictHostKeyChecking=no"
|
||||||
|
"-o ConnectTimeout=$ConnectTimeout"
|
||||||
|
"-o ConnectionAttempts=$ConnectionAttempts"
|
||||||
|
"-o LogLevel=ERROR"
|
||||||
|
"-o ServerAliveInterval=$ServerAliveInterval"
|
||||||
|
"${env:SSHUSER}@${HostName}"
|
||||||
|
)
|
||||||
|
$sshPassOptions = $sshArg -join " "
|
||||||
|
bash -c "$sshPassOptions \""$Command\"" 2>&1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-WithRetry {
|
||||||
|
param(
|
||||||
|
[scriptblock] $Command,
|
||||||
|
[scriptblock] $BreakCondition,
|
||||||
|
[int] $RetryCount = 20,
|
||||||
|
[int] $Seconds = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
while ($RetryCount -gt 0) {
|
||||||
|
if ($Command) {
|
||||||
|
$result = & $Command
|
||||||
|
}
|
||||||
|
|
||||||
|
if (& $BreakCondition) {
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
$RetryCount--
|
||||||
|
if ($RetryCount -eq 0) {
|
||||||
|
Write-Error "No more attempts left: $BreakCondition"
|
||||||
|
}
|
||||||
|
Write-Host "`t [/] Waiting $Seconds seconds before retrying. Retries left: $RetryCount"
|
||||||
|
Start-Sleep -Seconds $Seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
$result
|
||||||
|
}
|
||||||
|
|
||||||
|
function Restart-VMSSH {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = "sudo reboot"
|
||||||
|
Invoke-SSHPassCommand -HostName $HostName -Command $command
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-StringWithFormat {
|
||||||
|
param(
|
||||||
|
[Parameter(ValuefromPipeline)]
|
||||||
|
[object] $string
|
||||||
|
)
|
||||||
|
|
||||||
|
process {
|
||||||
|
($string | Out-String).Trim().split("`n") | ForEach-Object { Write-Host "`t $_" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-AutoLogon {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $VMName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $UserName
|
||||||
|
)
|
||||||
|
|
||||||
|
Invoke-WithRetry -BreakCondition {
|
||||||
|
# pwsh crashes if it invokes directly
|
||||||
|
# https://github.com/dotnet/runtime/issues/59059
|
||||||
|
$ankaUser = "" | bash -c "anka run $VMName /usr/bin/id -nu"
|
||||||
|
$UserName -eq $ankaUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-SoftwareUpdate {
|
||||||
|
param (
|
||||||
|
[string] $UpdateProcessName = "softwareupdate"
|
||||||
|
)
|
||||||
|
|
||||||
|
$command = {
|
||||||
|
$updateProcess = (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue).id
|
||||||
|
if ($updateProcess) {
|
||||||
|
# Workaround to get commandline param as it doesn't work for macOS atm https://github.com/PowerShell/PowerShell/issues/13943
|
||||||
|
$processName = /bin/ps -o command= $updateProcess
|
||||||
|
Write-Host "`t[*] Another software update process with '$updateProcess' id is in place with the following arguments '$processName'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$condition = {
|
||||||
|
$null -eq (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue)
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-WithRetry -Command $command -BreakCondition $condition
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-SSHPort {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ipaddress] $IPAddress,
|
||||||
|
|
||||||
|
[int] $Port = 22,
|
||||||
|
[int] $Timeout = 2000
|
||||||
|
)
|
||||||
|
|
||||||
|
Invoke-WithRetry -Command {$true} -BreakCondition {
|
||||||
|
try {
|
||||||
|
$client = [System.Net.Sockets.TcpClient]::new()
|
||||||
|
$client.ConnectAsync($IPAddress, $Port).Wait($Timeout)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$false
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$client.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-LoginWindow {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[ValidateNotNullOrEmpty()]
|
||||||
|
[string] $HostName,
|
||||||
|
|
||||||
|
[int] $RetryCount = 60,
|
||||||
|
[int] $Seconds = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
$condition = {
|
||||||
|
$psCommand = "/bin/ps auxww"
|
||||||
|
$lw = "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow"
|
||||||
|
$ctk = "/System/Library/Frameworks/CryptoTokenKit.framework/ctkahp.bundle/Contents/MacOS/ctkahp"
|
||||||
|
$proc = Invoke-SSHPassCommand -HostName $HostName -Command $psCommand | Out-String
|
||||||
|
$proc.Contains($lw) -and $proc.Contains($ctk)
|
||||||
|
}
|
||||||
|
Invoke-WithRetry -RetryCount $RetryCount -Seconds $Seconds -BreakCondition $condition
|
||||||
|
}
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
[Parameter(Mandatory)]
|
|
||||||
[ValidateNotNullOrEmpty()]
|
|
||||||
[Version] $MacOSVersion,
|
|
||||||
|
|
||||||
[ValidateNotNullOrEmpty()]
|
|
||||||
[String] $TemplateUsername,
|
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
|
||||||
[ValidateNotNullOrEmpty()]
|
|
||||||
[String] $TemplatePassword,
|
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
|
||||||
[ValidateNotNullOrEmpty()]
|
|
||||||
[String] $RegistryUrl,
|
|
||||||
|
|
||||||
[Bool] $BetaSearch = $false,
|
|
||||||
[Int] $CpuCount = 6,
|
|
||||||
[Int] $RamSizeGb = 7,
|
|
||||||
[Int] $DiskSizeGb = 300,
|
|
||||||
[String] $DisplayResolution = "1920x1080"
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
function Get-MacOSInstallers {
|
|
||||||
param (
|
|
||||||
[version] $MacOSVersion,
|
|
||||||
[bool] $BetaSearch = $false
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enroll machine to DeveloperSeed if we need beta and unenroll otherwise
|
|
||||||
$seedutil = "/System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil"
|
|
||||||
if ($BetaSearch) {
|
|
||||||
Write-Host "Beta Version requested. Enrolling machine to DeveloperSeed"
|
|
||||||
sudo $seedutil enroll DeveloperSeed | Out-Null
|
|
||||||
} else {
|
|
||||||
Write-Host "Reseting the seed before requesting stable versions"
|
|
||||||
sudo $seedutil unenroll | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate there is no softwareupdate at the moment
|
|
||||||
Test-SoftwareUpdate
|
|
||||||
|
|
||||||
# Validate availability OSVersion
|
|
||||||
$availableVersions = Get-AvailableVersions -IsBeta $BetaSearch
|
|
||||||
$macOSName = $availableVersions.Where{ $_.OSVersion -eq $MacOSVersion }.OSName
|
|
||||||
if (-not $macOSName) {
|
|
||||||
Write-Host "Requested macOS '$MacOSVersion' version not found in the list of available installers. Available versions are:`n$($availableVersions.OSVersion)"
|
|
||||||
Write-Host 'Make sure to pass "-BetaSearch $true" if you need a beta version installer'
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$installerPathPattern = "/Applications/Install*${macOSName}.app"
|
|
||||||
if (Test-Path $installerPathPattern) {
|
|
||||||
$previousInstallerPath = Get-Item -Path $installerPathPattern
|
|
||||||
Write-Host "Removing '$previousInstallerPath' installation app before downloading the new one"
|
|
||||||
sudo rm -rf "$previousInstallerPath"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Download macOS installer
|
|
||||||
Write-Host "Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
|
|
||||||
$result = Invoke-WithRetry { softwareupdate --fetch-full-installer --full-installer-version $MacOSVersion } {$LASTEXITCODE -eq 0} | Out-String
|
|
||||||
if (-not $result.Contains("Install finished successfully")) {
|
|
||||||
Write-Host "[Error]: failed to fetch $MacOSVersion macOS '$MacOSVersion' `n$result"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$installerPath = Get-Item -Path $installerPathPattern
|
|
||||||
Write-Host "Installer successfully downloaded to '$installerPath'"
|
|
||||||
|
|
||||||
return $installerPath.FullName
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-WithRetry {
|
|
||||||
param(
|
|
||||||
[scriptblock] $Command,
|
|
||||||
[scriptblock] $BreakCondition,
|
|
||||||
[int] $RetryCount = 20,
|
|
||||||
[int] $Seconds = 60
|
|
||||||
)
|
|
||||||
|
|
||||||
while ($RetryCount -gt 0) {
|
|
||||||
$result = & $Command
|
|
||||||
|
|
||||||
if (& $BreakCondition) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
$RetryCount--
|
|
||||||
Write-Host "Waiting $Seconds seconds before retrying. Retries left: $RetryCount"
|
|
||||||
Start-Sleep -Seconds $Seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
$result
|
|
||||||
}
|
|
||||||
|
|
||||||
function Get-AvailableVersions {
|
|
||||||
param (
|
|
||||||
[Int] $RetryCount = 20,
|
|
||||||
[Int] $RetryInterval = 60,
|
|
||||||
[Bool] $IsBeta = $false
|
|
||||||
)
|
|
||||||
|
|
||||||
if ($IsBeta) {
|
|
||||||
$searchPostfix = " beta"
|
|
||||||
}
|
|
||||||
|
|
||||||
$softwareUpdates = Invoke-WithRetry { softwareupdate --list-full-installers | Where-Object { $_.Contains("Title: macOS") -and $_ -match $searchPostfix } } { {$LASTEXITCODE -eq 0}}
|
|
||||||
$allVersions = $softwareUpdates -replace "(\* )?(Title|Version|Size):" | ConvertFrom-Csv -Header OsName, OsVersion
|
|
||||||
|
|
||||||
return $allVersions
|
|
||||||
}
|
|
||||||
|
|
||||||
function Test-SoftwareUpdate {
|
|
||||||
param (
|
|
||||||
[String] $UpdateProcessName = "softwareupdate",
|
|
||||||
[Int] $RetryCount = 20,
|
|
||||||
[Int] $RetryInterval = 60
|
|
||||||
)
|
|
||||||
|
|
||||||
$command = {
|
|
||||||
$updateProcess = (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue).id
|
|
||||||
if ($updateProcess) {
|
|
||||||
# Workaround to get commandline param as it doesn't work for macOS atm https://github.com/PowerShell/PowerShell/issues/13943
|
|
||||||
$processName = ps -o command= $updateProcess
|
|
||||||
Write-Host "Another software update process is in place with the following arguments '$processName', wait $RetryInterval seconds, $RetryCount attempts left"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$condition = {
|
|
||||||
$null -eq (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue)
|
|
||||||
}
|
|
||||||
|
|
||||||
Invoke-WithRetry -Command $command -BreakCondition $condition
|
|
||||||
}
|
|
||||||
|
|
||||||
function New-AnkaVMTemplate {
|
|
||||||
param (
|
|
||||||
[String] $InstallerPath,
|
|
||||||
[String] $ShortMacOSVersion,
|
|
||||||
[String] $TemplateName,
|
|
||||||
[String] $TemplateUsername,
|
|
||||||
[String] $TemplatePassword,
|
|
||||||
[Int] $CpuCount,
|
|
||||||
[Int] $RamSizeGb,
|
|
||||||
[Int] $DiskSizeGb,
|
|
||||||
[String] $DisplayResolution
|
|
||||||
)
|
|
||||||
|
|
||||||
$isTemplateExists = (Invoke-Anka { anka --machine-readable list } | ConvertFrom-Json).body.name -eq $templateName
|
|
||||||
if ($isTemplateExists) {
|
|
||||||
Write-Host "Deleting existed template with name '$templateName' before creating a new one"
|
|
||||||
Invoke-Anka { anka delete $templateName --yes }
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Creating Anka VM template with name '$TemplateName' and user $TemplateUsername"
|
|
||||||
$env:ANKA_DEFAULT_USER = $TemplateUsername
|
|
||||||
$env:ANKA_DEFAULT_PASSWD = $TemplatePassword
|
|
||||||
$env:ANKA_CREATE_SUSPEND = 0
|
|
||||||
Write-Host "Cpu Count: $CpuCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $InstallerPath, TemplateName: $templateName"
|
|
||||||
Invoke-Anka { anka create --cpu-count $CpuCount --ram-size "${RamSizeGb}G" --disk-size "${DiskSizeGb}G" --app $InstallerPath $templateName }
|
|
||||||
|
|
||||||
# Apple Metal is available starting from Big Sur
|
|
||||||
if (-not $ShortMacOSVersion.StartsWith("10.")) {
|
|
||||||
Write-Host "Enabling Graphics Acceleration with Apple Metal for $templateName"
|
|
||||||
Invoke-Anka { anka modify $templateName set display -c pg }
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Setting screen resolution to $DisplayResolution for $templateName"
|
|
||||||
Invoke-Anka { anka modify $templateName set display -r $DisplayResolution }
|
|
||||||
|
|
||||||
return $templateName
|
|
||||||
}
|
|
||||||
|
|
||||||
function Add-AnkaImageToRegistry {
|
|
||||||
param (
|
|
||||||
[String] $RegistryUrl,
|
|
||||||
[String] $ShortMacOSVersion,
|
|
||||||
[String] $TemplateName
|
|
||||||
)
|
|
||||||
|
|
||||||
Write-Host "Pushing image to the registry..."
|
|
||||||
Invoke-Anka { anka registry -a $RegistryUrl push -t $ShortMacOSVersion $TemplateName }
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-Anka {
|
|
||||||
param (
|
|
||||||
[scriptblock] $Cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
& $Cmd
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Error "There is an error during command execution"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Get-ShortMacOSVersion {
|
|
||||||
param (
|
|
||||||
[Version] $MacOSVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
# Take Major.Minor version for macOS 10 (10.14 or 10.15) and Major for all further versions
|
|
||||||
if ($MacOSVersion.Major -eq 10) {
|
|
||||||
$shortMacOSVersion = $MacOSVersion.ToString(2)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$shortMacOSVersion = $MacOSVersion.Major
|
|
||||||
}
|
|
||||||
|
|
||||||
return $shortMacOSVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
$macOSInstaller = Get-MacOSInstallers -MacOSVersion $MacOSVersion -BetaSearch $BetaSearch
|
|
||||||
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
|
||||||
$templateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
|
||||||
New-AnkaVMTemplate -InstallerPath $macOSInstaller `
|
|
||||||
-ShortMacOSVersion $shortMacOSVersion `
|
|
||||||
-TemplateName $templateName `
|
|
||||||
-TemplateUsername $TemplateUsername `
|
|
||||||
-TemplatePassword $TemplatePassword `
|
|
||||||
-CpuCount $CpuCount `
|
|
||||||
-RamSizeGb $RamSizeGb `
|
|
||||||
-DiskSizeGb $DiskSizeGb `
|
|
||||||
-DisplayResolution $DisplayResolution
|
|
||||||
|
|
||||||
Add-AnkaImageToRegistry -RegistryUrl $registryUrl -ShortMacOSVersion $shortMacOSVersion -TemplateName $templateName
|
|
||||||
Reference in New Issue
Block a user