Moc.psm1
######################################################################################### # # Copyright (c) Microsoft Corporation. All rights reserved. # # Moc Module # ######################################################################################### #requires -runasadministrator using module .\Common.psm1 #region Module Constants $moduleName = "Moc" $moduleVersion = "1.0.1" #endregion #region Download catalog constants # Default until MOC has it's own catalog $catalogName = "aks-hci-stable-catalogs-ext" $ringName = "stable" $productName = "mocstack" #endregion #region Global Config if (!$global:config) { $global:config = @{} } #region Script Constants $mocBinaries = @( "wssdagent.exe" "wssdcloudagent.exe" "mocctl.exe" "nodectl.exe" ) $requiredServerFeatures = @( "Hyper-V", "Hyper-V-PowerShell", "RSAT-Clustering-PowerShell" ) $svcFailureRestartMs = 60000 $svcFailureResetSecond = 86400 $svcNodeAgentDependency = "winmgmt/vmms" $script:defaultTokenExpiryDays = 60 $global:cloudAgentRegistryPath = "HKLM:\SOFTWARE\Microsoft\wssdcloudagent" $global:nodeAgentRegistryPath = "HKLM:\SOFTWARE\Microsoft\wssdagent" $global:cloudAgentCertName = "cloudagent.pem" $global:nodeAgentCertName = "nodeagent.pem" $global:cloudloginYAMLName = "cloudlogin.yaml" $global:nodeloginYAMLName = "nodelogin.yaml" $global:nodeToCloudloginYAMLName = "nodeToCloudlogin.yaml" $mocBinariesMap = @{ $global:nodeCtlBinary = $global:nodeCtlFullPath; $global:cloudCtlBinary = $global:cloudCtlFullPath; $global:cloudAgentBinary = $global:cloudAgentFullPath; $global:nodeAgentBinary = $global:nodeAgentFullPath; } #endregion #region # Install Event Log New-ModuleEventLog -moduleName $moduleName #endregion #region Private Function function Initialize-MocConfiguration { <# .DESCRIPTION Initialize Moc Configuration Wipes off any existing cached configuration #> if ($global:config.ContainsKey($moduleName)) { $global:config.Remove($moduleName) } $global:config += @{ $moduleName = @{ "cloudAgentAuthorizerPort" = 0 "cloudAgentPort" = 0 "cloudConfigLocation" = $global:defaultCloudConfigLocation "cloudFqdn" = "localhost" "cloudLocation" = "" "cloudServiceCidr" = "" "clusterRoleName" = "" "deploymentType" = [DeploymentType]::None "dnsservers" = "" "forceDnsReplication" = $false "gateway" = "" "imageDir" = "" "insecure" = $false "installationPackageDir" = "" "installState" = [InstallState]::NotInstalled "ipaddressprefix" = "" "k8snodeippoolstart" = "" "k8snodeippoolend" = "" "macPoolEnd" = "" "macpoolname" = "" "macPoolStart" = "" "manifestCache" = [io.path]::GetTempFileName() "mocCertLocation" = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:cloudAgentCertName) "mocLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:cloudloginYAMLName) "moduleVersion" = $moduleVersion "nodeAgentAuthorizerPort" = 0 "nodeAgentPort" = 0 "nodeCertLocation" = [io.Path]::Combine($global:defaultCloudConfigLocation, $global:nodeAgentCertName) "nodeConfigLocation" = $defaultNodeConfigLocation "nodeLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeloginYAMLName) "nodeToCloudLoginYAML" = [io.Path]::Combine($global:defaultNodeConfigLocation, $global:nodeToCloudloginYAMLName) "skipHostLimitChecks" = $false "skipUpdates" = $false "sshPrivateKey" = "" "sshPublicKey" = "" "stagingShare" = "" "tokenExpiryDays" = 0 "useStagingShare" = $false "version" = "" "vlanid" = 0 "vnetName" = "" "vswitchName" = "" "vnetvippoolend" = "" "vnetvippoolstart" = "" "workingDir" = "" "catalog" = "" "ring" = "" "proxyServerCertFile" = "" "proxyServerHTTP" = "" "proxyServerHTTPS" = "" "proxyServerNoProxy" = "" "proxyServerPassword" = "" "proxyServerUsername" = "" "certificateValidityFactor" = 0.0 }; } } #endregion # Initialize Initialize-MocConfiguration #endregion #region Exported Functions function Install-Moc { <# .DESCRIPTION The main deployment method for MOC. This function is responsible for provisioning files, deploying the agents. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Initialize-MocEnvironment -createConfigIfNotPresent -activity $activity $curState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState" if ($curState) { switch ($curState) { ([InstallState]::Installed) { Write-Status -moduleName $moduleName "MOC is already installed" Write-SubStatus -moduleName $moduleName "Please use Reset-Moc to reinstall or Uninstall-Moc to uninstall." return } ([InstallState]::Installing) { Write-Status -moduleName $moduleName "MOC is currently being installed. If thats not the case, please run Uninstall-Moc and try again" return } ([InstallState]::NotInstalled) { # Fresh install break } Default { # Cleanup partial installs from previous attempts Uninstall-Moc -activity $activity } } } try { Install-MocInternal -activity $activity } catch [Exception] { Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" Uninstall-Moc -SkipConfigCleanup:$True -activity $activity throw $_ } Write-Status -moduleName $moduleName "Done." } function Reset-Moc { <# .DESCRIPTION Cleans up an existing MOC deployment and reinstalls everything. This isn't equivalent to executing 'Uninstall-Moc' followed by 'Install-Moc' as Reset-Moc will preserve existing configuration settings and any downloaded images. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Initialize-MocEnvironment -activity $activity Uninstall-Moc -SkipConfigCleanup:$True -activity $activity Install-MocInternal -activity $activity Write-Status -moduleName $moduleName "Done." } function Uninstall-Moc { <# .DESCRIPTION Removes a MOC deployment. .PARAMETER SkipConfigCleanup Skip removal of the configurations after uninstall. After Uninstall, you have to Set-MocConfig to install again. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [Switch]$SkipConfigCleanup, [String]$activity = $MyInvocation.MyCommand.Name ) try { Initialize-MocEnvironment -activity $activity } catch [Exception] { Write-ModuleEventLog -moduleName $moduleName -entryType Warning -eventId 2 -message "$activity - $_" } Set-MocConfigValue -name "installState" -value ([InstallState]::Uninstalling) try { # Confirm-Remoting - We assume that the remoting check was already done and nothing has changed Reset-Host -removeAll -skipConfigDeletion:$SkipConfigCleanup.IsPresent } catch [Exception] { Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled) if (!$SkipConfigCleanup.IsPresent) { Reset-Configuration -moduleName $moduleName } Write-Status -moduleName $moduleName "Done." } function Initialize-MocNode { <# .SYNOPSIS Run checks on every physical node to see if all requirements are satisfied to install. .DESCRIPTION Run checks on every physical node to see if all requirements are satisfied to install. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $("Initializing MOC Node") #Initialize-MocEnvironment Enable-Remoting Confirm-Remoting -localhostOnly Test-ForWindowsFeatures -features $script:requiredServerFeatures -nodeName $env:computername Write-Status -moduleName $moduleName "Done." } function Update-Moc { <# .DESCRIPTION Update MOC to specified version. If not use the latest version .PARAMETER version The Optional version to update to. If none specified, it would be updated to latest .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$version, [String]$activity = $MyInvocation.MyCommand.Name ) $currentVersion = Get-MocVersion Write-SubStatus -moduleName $moduleName "Current MOC Version: $currentVersion" # If no version is specified, try to move to the latest if (!$version) { # If no version is specified, use the latest $release = Get-LatestRelease -moduleName $moduleName $version = $release.Version Set-MocConfigValue -name "version" -value $version Get-ProductRelease -Version $version -module $moduleName | Out-Null } else { if ($version -eq $currentVersion) { Write-SubStatus -moduleName $moduleName "Already in the expected version $version" return } Get-ProductRelease -Version $version -module $moduleName | Out-Null Set-MocConfigValue -name "version" -value $version } $workingDir = $global:config[$moduleName]["workingDir"] try { $installationPackageDir = ([io.Path]::Combine($workingDir, $version)) Set-MocConfigValue -name "installationPackageDir" -value $installationPackageDir New-Item -ItemType Directory -Force -Path $installationPackageDir | Out-Null Update-MocInternal -activity $activity } catch { Set-MocConfigValue -name "version" -value $currentVersion Set-MocConfigValue -name "installationPackageDir" -value $([io.Path]::Combine($workingDir, $currentVersion)) # Revert Update-MocInternal -activity $activity throw } } function Update-MocInternal { <# .DESCRIPTION Upgrade MOC to selected version. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Set-MocConfigValue -name "installState" -value ([InstallState]::Updating) $version = Get-MocVersion Write-StatusWithProgress -activity $activity -module $moduleName -status $("Updating to version $version") New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null Get-MocRelease -version $version -activity $activity # 1. Stop the agents # 2. Upgrade Agents $isMultiNodeDeployment = Test-MultiNodeDeployment if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction SilentlyContinue | ForEach-Object { Stop-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction SilentlyContinue } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { Stop-Service wssdagent -Force } Update-MocNode -nodeName $_.Name -activity $activity } } else { Stop-Service wssdcloudagent -Force Stop-Service wssdagent -Force Update-MocNode -nodeName ($env:computername) -activity $activity } # 3. Start Cloud agent if ($isMultiNodeDeployment) { Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | ForEach-Object { Start-ClusterGroup -InputObject $_ -IgnoreLocked -ErrorAction Stop } } else { Start-Service wssdcloudagent -ErrorAction SilentlyContinue -WarningAction:SilentlyContinue } # We must wait for the generic service VIP to become usable $isCAAvailable = Wait-ForCloudAgentEndpoint -timeout 60 -activity $activity if (-not $isCAAvailable) { throw $("CloudAgent is unreachable after update") } # 4. Start Node agents if ($isMultiNodeDeployment) { $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"] Get-ClusterNode -ErrorAction Stop | ForEach-Object { if (-not $global:config[$modulename]["insecure"]) { $nodeIdentity = Invoke-MocIdentityRotate -name $_.Name } Invoke-Command -ComputerName $_ -ScriptBlock { $nodeToCloudLoginFile = $args[0] $cloudagentlogin = $args[1] Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err Start-Service wssdagent -WarningAction:SilentlyContinue } -ArgumentList $nodeCloudLoginFile, $nodeIdentity } } else { $nodeCloudLoginFile = $global:config[$modulename]["nodeToCloudLoginYAML"] if (-not $global:config[$modulename]["insecure"]) { $nodeIdentity = Invoke-MocIdentityRotate -name ($env:computername) } Set-Content -Path $nodeCloudLoginFile -Value $nodeIdentity -ErrorVariable err Start-Service wssdagent -ErrorAction SilentlyContinue -WarningAction:SilentlyContinue } $mocLocation = $global:config[$modulename]["cloudLocation"] $areNodesActive = Wait-ForActiveNodes -location $mocLocation -activity $activity if (-not $areNodesActive) { throw $("Nodes have not reached Active state") } Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) } function Get-MocVersion { <# .DESCRIPTION Get the current MOC version .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Initialize-MocEnvironment -activity $activity return $global:config[$modulename]["version"] } function Update-MocNode { <# .DESCRIPTION Upgrade Moc binaries to latest on a node .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [Parameter(Mandatory=$true)] [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status "Upgrading Node $nodeName" Install-MocBinaries -nodeName $nodeName } function Install-MocBinaries { <# .DESCRIPTION Copies Moc binaries to a node .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName ) Install-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName } function Uninstall-MocBinaries { <# .DESCRIPTION Copies Moc binaries to a node .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName ) Uninstall-Binaries -nodeName $nodeName -binariesMap $mocBinariesMap -module $moduleName } function Get-MocConfig { <# .DESCRIPTION Loads and returns the current MOC configuration. .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [String]$activity = $MyInvocation.MyCommand.Name ) Import-MocConfig -activity $activity Write-StatusWithProgress -activity $activity -module $moduleName -status "Get MOC Configuration" # Fixup the type for readability reasons $global:config[$modulename]["installState"] = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState" $global:config[$modulename]["deploymentType"] = Get-ConfigurationValue -module $moduleName -type ([Type][DeploymentType]) -name "deploymentType" return $global:config[$modulename] } function Import-MocConfig { <# .DESCRIPTION Loads a configuration from persisted storage. If no configuration is present then a default configuration can be optionally generated and persisted. ' .PARAMETER activity Activity name to use when updating progress #> [CmdletBinding()] param ( [switch]$createIfNotPresent, [string]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status "Importing Configuration" if (Test-Configuration -moduleName $moduleName) { Import-Configuration -moduleName $moduleName } else { throw "[$moduleName] This machine does not appear to be configured for deployment." } if ($global:config[$moduleName]["cloudConfigLocation"] -ine $defaultCloudConfigLocation) { $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName)) $currentValue = Get-MocConfigValue -name "mocCertLocation" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "mocCertLocation" -value $tmp } $tmp = ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName)) $currentValue = Get-MocConfigValue -name "mocLoginYAML" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "mocLoginYAML" -value $tmp } } if ($global:config[$moduleName]["nodeConfigLocation"] -ine $defaultNodeConfigLocation) { $tmp = ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName)) $currentValue = Get-MocConfigValue -name "nodeCertLocation" if ($tmp -ne $currentValue) { Set-MocConfigValue -name "nodeCertLocation" -value $tmp } } Write-StatusWithProgress -activity $activity -module $moduleName -status "Importing Configuration Completed" Write-Status -moduleName $moduleName "Validating configuration" if (-not (Test-LocalFilePath -path $global:config[$moduleName]["nodeConfigLocation"])) { throw $("The parameter nodeConfigLocation must specify a local file path. Please re-run configuration.") } } function Set-MocConfig { <# .DESCRIPTION Configures MOC by persisting the specified parameters to the registry. Any parameter which is not explictly provided by the user will be defaulted. #> [CmdletBinding()] param ( [string] $activity = $MyInvocation.MyCommand.Name, [String] $workingDir = $global:defaultWorkingDir, [String] $imageDir, [String] $version = $global:version, [String] $stagingShare = $global:defaultStagingShare, [String] $cloudConfigLocation = $global:defaultCloudConfigLocation, [String] $nodeConfigLocation = $global:defaultNodeConfigLocation, [String] $cloudLocation = $global:defaultCloudLocation, [Parameter(Mandatory=$true)] [VirtualNetwork] $vnet, [int] $nodeAgentPort = $global:defaultNodeAgentPort, [int] $nodeAgentAuthorizerPort = $global:defaultNodeAuthorizerPort, [int] $cloudAgentPort = $global:defaultCloudAgentPort, [int] $cloudAgentAuthorizerPort = $global:defaultCloudAuthorizerPort, [int] $tokenExpiryDays = $script:defaultTokenExpiryDays, [String] $clusterRoleName = $($global:cloudAgentAppName + "-" + [guid]::NewGuid()), [Alias("cloudServiceIP")] [String] $cloudServiceCidr = "", [String] $sshPublicKey, [Switch] $skipUpdates, [Switch] $skipHostLimitChecks, [Switch] $skipRemotingChecks, [Switch] $insecure, [Switch] $forceDnsReplication, [String] $macPoolStart, [String] $macPoolEnd, [switch] $useStagingShare, [String] $catalog = $script:catalogName, [String] $ring = $script:ringName, [ProxySettings] $proxySettings = $null, [String] $deploymentId = [Guid]::NewGuid().ToString(), [parameter(DontShow)] [float] $certificateValidityFactor = $global:certificateValidityFactor ) # First get the existing config and find out if we are in the middle of something. try { Import-MocConfig -activity $activity } catch {} $currentState = Get-ConfigurationValue -module $moduleName -type ([Type][InstallState]) -name "installState" if ($currentState) { switch ($currentState) { ([InstallState]::NotInstalled) { # Fresh install break } Default { Write-Status -moduleName $moduleName "MOC is currently in $currentState state" throw "Cannot set new $moduleName configuration when in this state [$currentState]" } } } # if we are good, Validate the input configuration Confirm-Configuration -workingDir $workingDir -skipHostLimitChecks:$skipHostLimitChecks.IsPresent ` -cloudConfigLocation $cloudConfigLocation -skipRemotingChecks:$skipRemotingChecks.IsPresent ` -useStagingShare:$useStagingShare.IsPresent -stagingShare $stagingShare Set-ProxyConfiguration -proxySettings $proxySettings -moduleName $moduleName # if we are good to proceed, create the requested configuration Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Creating configuration for $moduleName" Set-MocConfigValue -name "workingDir" -value $workingDir Set-MocConfigValue -name "manifestCache" -value ([io.Path]::Combine($workingDir, $("$catalog.json"))) if (!$imageDir) { $imageDir = [io.Path]::Combine($workingDir, $global:imageDirectoryName) } Set-MocConfigValue -name "imageDir" -value $imageDir Set-MocConfigValue -name "moduleVersion" -value $moduleVersion Set-MocConfigValue -name "installState" -value ([InstallState]::NotInstalled) Set-MocConfigValue -name "stagingShare" -value $stagingShare Set-MocConfigValue -name "cloudConfigLocation" -value $cloudConfigLocation Set-MocConfigValue -name "nodeConfigLocation" -value $nodeConfigLocation Set-MocConfigValue -name "cloudLocation" -value $cloudLocation Set-MocConfigValue -name "catalog" -value $catalog Set-MocConfigValue -name "ring" -value $ring Set-MocConfigValue -name "deploymentId" -value $deploymentId Set-MocConfigValue -name "certificateValidityFactor" -value $certificateValidityFactor Set-VNetConfiguration -module $moduleName -vnet $vnet if ($sshPublicKey) { # Set the SSH Key of moc Module to the one already being passed Set-MocConfigValue -name "sshPublicKey" -value $sshPublicKey Set-MocConfigValue -name "sshPrivateKey" ` -value ([io.Path]::Combine([io.Path]::GetDirectoryName($sshPublicKey), [io.Path]::GetFileNameWithoutExtension($sshPublicKey))) } else { # There seems to be an issue with ssh_keygen. It doesnt create public key when using share. if ($workingDir.StartsWith("\\")) { Set-MocConfigValue -name "sshPublicKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa.pub") Set-MocConfigValue -name "sshPrivateKey" -value ("$env:USERPROFILE\.ssh\akshci_rsa") } else { Set-MocConfigValue -name "sshPublicKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa.pub")) Set-MocConfigValue -name "sshPrivateKey" -value ([io.Path]::Combine($workingDir, ".ssh", "akshci_rsa")) } } Set-MocConfigValue -name "nodeAgentPort" -value $nodeAgentPort Set-MocConfigValue -name "nodeAgentAuthorizerPort" -value $nodeAgentAuthorizerPort Set-MocConfigValue -name "cloudAgentPort" -value $cloudAgentPort Set-MocConfigValue -name "cloudAgentAuthorizerPort" -value $cloudAgentAuthorizerPort Set-MocConfigValue -name "clusterRoleName" -value $clusterRoleName Set-MocConfigValue -name "skipUpdates" -value $skipUpdates.IsPresent Set-MocConfigValue -name "skipHostLimitChecks" -value $skipHostLimitChecks.IsPresent Set-MocConfigValue -name "insecure" -value $insecure.IsPresent Set-MocConfigValue -name "forceDnsReplication" -value $forceDnsReplication.IsPresent Set-MocConfigValue -name "useStagingShare" -value $useStagingShare.IsPresent Set-MocConfigValue -name "macPoolStart" -value $macPoolStart Set-MocConfigValue -name "macPoolEnd" -value $macPoolEnd Set-MocConfigValue -name "cloudServiceCidr" -value $cloudServiceCidr if (-not $version) { $version = Get-ConfigurationValue -Name "version" -module $moduleName if (-not $version) { # If no version is specified, use the latest from the product catalog $release = Get-LatestRelease -moduleName $moduleName $version = $release.Version Set-MocConfigValue -name "version" -value $version } } else { Get-LatestCatalog -moduleName $moduleName | Out-Null # This clears the cache Get-ProductRelease -Version $version -module $moduleName | Out-Null Set-MocConfigValue -name "version" -value $version } Set-MocConfigValue -name "installationPackageDir" -value ([io.Path]::Combine($workingDir, $version)) Set-MocConfigValue -name "mocCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentCertName)) Set-MocConfigValue -name "nodeCertLocation" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeAgentCertName)) Set-MocConfigValue -name "mocLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["cloudConfigLocation"], $global:cloudloginYAMLName)) Set-MocConfigValue -name "nodeLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeloginYAMLName)) Set-MocConfigValue -name "nodeToCloudLoginYAML" -value ([io.Path]::Combine($global:config[$moduleName]["nodeConfigLocation"], $global:nodeToCloudloginYAMLName)) Set-MocConfigValue -name "tokenExpiryDays" -value $tokenExpiryDays Initialize-Directories Set-DeploymentType # There is one use case, where configuration is set when it is lost and restored. # Setting the install state appropriately if ((Test-Path $global:cloudCtlFullPath)) { $isCAAvailable = Wait-ForCloudAgentEndpoint -timeout 1 if ($isCAAvailable) { Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) } Write-SubStatus -moduleName $moduleName "Existing configuration has been loaded`n" } else { Write-SubStatus -moduleName $moduleName "New configuration has been saved`n" } Save-ConfigurationDirectory -moduleName $moduleName -WorkingDir $workingDir Save-Configuration -moduleName $moduleName Write-SubStatus -moduleName $moduleName "New configuration for module $moduleName has been saved`n" } function Set-MocConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) Set-ConfigurationValue -name $name -value $value -module $moduleName } function Get-MocConfigValue { <# .DESCRIPTION Persists a configuration value to the registry .PARAMETER name Name of the configuration value .PARAMETER value Value to be persisted #> param ( [String] $name, [Object] $value ) return Get-ConfigurationValue -name $name -module $moduleName } function Get-MocHyperThreadingEnabled { <# .DESCRIPTION Check if HyperThreading is enabled on the current node comparing number of actual cores and the logical processors #> $hyperThreadingEnabled = $true $moclocation = $global:config[$modulename]["cloudLocation"] $coresInfo = Invoke-MocCommand " cloud node list -o json --query ""[?properties.statuses.Info]"" --location $moclocation" if ($null -ne $coresInfo) { $cores = [UInt32]::Parse([regex]::Match($coresInfo, "cores:(\d+)").captures.groups[1].value) $logicalProcessors = [UInt32]::Parse([regex]::Match($coresInfo, "logicalprocessors:(\d+)").captures.groups[1].value) $hyperthreadingEnabled = ($cores -ne $logicalProcessors) } return $hyperthreadingEnabled } #region Installation and Provisioning functions function Install-MocInternal { <# .DESCRIPTION The main deployment method for MOC. This function is responsible for provisioning files, deploying the agents. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Set-MocConfigValue -name "installState" -value ([InstallState]::Installing) try { Reset-Host -removeAll -skipImageDeletion -skipConfigDeletion -activity $activity # Calling Initialize again, since Reset-Host cleans up everything Initialize-Directories Get-MocRelease -version $(Get-MocVersion) -activity $activity Initialize-Cloud -activity $activity Test-CloudConfiguration } catch { Set-MocConfigValue -name "installState" -value ([InstallState]::InstallFailed) throw $_ } Set-MocConfigValue -name "installState" -value ([InstallState]::Installed) Write-Status -moduleName $moduleName "MOC installation is complete!" } function Initialize-MocEnvironment { <# .DESCRIPTION Executes steps to prepare the environment for day 0 operations. .PARAMETER createConfigIfNotPresent Whether the call should create a new deployment configuration if one is not already present. .PARAMETER activity Activity name to use when updating progress #> param ( [Switch]$createConfigIfNotPresent, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status "Discovering configuration" Import-MocConfig -createIfNotPresent:($createConfigIfNotPresent.IsPresent) Write-StatusWithProgress -activity $activity -module $moduleName -status "Applying configuration" Initialize-Environment -checkForUpdates:$false -moduleName $script:moduleName } function Initialize-Cloud { <# .DESCRIPTION Provision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $("Provisioning MOC Cloud") # 1. Provision Moc Agents Install-MocAgents -activity $activity # 2. Wait for cloud nodes to be active Write-StatusWithProgress -activity $activity -module $moduleName -status $("Provisioning cloud resources") $mocLocation = $global:config[$modulename]["cloudLocation"] $areNodesActive = Wait-ForActiveNodes -location $mocLocation -activity $activity if (-not $areNodesActive) { throw $("Nodes have not reached Active state") } # 3. Provision Cloud MacPool $mocLocation = $global:config[$modulename]["cloudLocation"] if (-not [string]::IsNullOrWhiteSpace($global:config[$modulename]["macPoolStart"])) { New-MocMacPool -name $global:cloudMacPool ` -location $($mocLocation) ` -macPoolStart $global:config[$modulename]["macPoolStart"] ` -macPoolEnd $global:config[$modulename]["macPoolEnd"] | Out-Null } # 5. Add cloud resources New-MocContainer -name $global:cloudStorageContainer -location $mocLocation -path $global:config[$modulename]["imageDir"] | Out-Null } function Install-MocAgents { <# .DESCRIPTION Provision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $("Provisioning MOC Agents") # 1. Provision Nodes if (Test-MultiNodeDeployment) { Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name Initialize-Node -nodeName $nodeName } } else { Initialize-Node -nodeName ($env:computername) } # 2. Provision Cloud Agent Install-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity New-MocLocation -name $global:config[$modulename]["cloudLocation"] | Out-Null $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML") # 4. Provision Nodes if (Test-MultiNodeDeployment) { $failoverCluster = Get-FailoverCluster New-MocCluster -name $failoverCluster.Name -fqdn $($failoverCluster.Name+"."+$failoverCluster.Domain) -location $global:config[$modulename]["cloudLocation"] | Out-Null Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = ${_}.Name Install-NodeAgent -nodeName $nodeName -activity $activity Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml } } else { Install-NodeAgent -nodeName ($env:computername) -activity $activity Invoke-NodeCommand "security login --loginpath $nodeLoginYaml --identity" } } function Install-CloudAgent { <# .DESCRIPTION Provision a Cloud Agent 1. Download Cloud Agent Binary 2. Configure Service 3. Start the Service .PARAMETER cloudAgentName Cloud agent name .PARAMETER activity Activity name to use when updating progress #> param ( [String]$cloudAgentName, [String]$activity = $MyInvocation.MyCommand.Name ) # Install the service as Cluster Gen Service Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Provisioning CloudAgent '$cloudAgentName'" $authArgs = "" if ($global:config[$modulename]["insecure"]) { $authArgs += "--debug" } $cloudFqdn = Get-CloudFqdn $cloudConfigPath = $global:config[$moduleName]["cloudConfigLocation"] $certificateValidityFactor = $global:config[$modulename]["certificateValidityFactor"] New-Item -ItemType Directory -Force -Path $cloudConfigPath | Out-Null Set-SecurePermissionFolder -Path $cloudConfigPath if (Test-MultiNodeDeployment) { $nodes = Get-ClusterNode -ErrorAction Stop $cloudConfigLocation = $global:config[$modulename]["cloudConfigLocation"] $clusterRoleName = $global:config[$modulename]["clusterRoleName"] $cloudServiceParameters = "--service $authArgs --basedir $cloudConfigLocation --cloudagentfqdn $cloudFqdn --dotfolderpath $cloudConfigLocation --objectdatastore ""Cluster-Registry"" --clusterresourcename ""$clusterRoleName"" --certificatevalidityfactor $certificateValidityFactor" $nodes.Name | ForEach-Object { Write-SubStatus -moduleName $moduleName $("Installing cloudagent service on " + $_) Invoke-Command -ComputerName $_ -ScriptBlock { $cloudAgentFullPath = $args[0] $cloudServiceParameters = $args[1] $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction SilentlyContinue New-Service -Name "wssdcloudagent" -BinaryPath """$cloudAgentFullPath"" $cloudServiceParameters" -StartupType "Manual" -DisplayName "WSSD Cloud Agent Service" | Out-Null } -ArgumentList $global:cloudAgentFullPath, $cloudServiceParameters } Write-SubStatus -moduleName $moduleName $("Adding wssdcloudagent cluster generic service role ("+$global:config[$modulename]["clusterRoleName"]+")") Add-FailoverClusterGenericRole -serviceDisplayName "MOC Cloud Agent Service" -serviceName wssdcloudagent -staticIpCidr $global:config[$modulename]["cloudServiceCidr"] -clusterGroupName $global:config[$modulename]["clusterRoleName"] -serviceParameters $cloudServiceParameters Request-DnsReplication } else { Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdcloudagent\' -force -ErrorAction SilentlyContinue New-Service -Name "wssdcloudagent" -BinaryPath $( """$global:cloudAgentFullPath"" " + " --service $authArgs --basedir "+$global:config[$modulename]["cloudConfigLocation"]+" --cloudagentfqdn $cloudFqdn --dotfolderpath "+$global:config[$modulename]["cloudConfigLocation"]+" --objectdatastore ""registry"" --certificatevalidityfactor $certificateValidityFactor") -StartupType "Automatic" -DisplayName "WSSD Cloud Agent Service" | Out-Null # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdcloudagent" actions= "restart/$script:svcFailureRestartMs/restart/$script:svcFailureRestartMs//0" reset= $script:svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } Start-Service "wssdcloudagent" -WarningAction:SilentlyContinue | Out-Null } # We must wait for the generic service VIP to become usable (i.e. wait for DNS to propogate) otherwise calls to moc will fail and the script will exit prematurely. $isCAAvailable = Wait-ForCloudAgentEndpoint -activity $activity if (-not $isCAAvailable) { throw $("CloudAgent is unreachable") } if (Test-MultiNodeDeployment) { if (-Not $global:config[$modulename]["insecure"]) { Write-SubStatus -moduleName $moduleName -msg "Replicating CloudConfig" # TODO: Switch to using individual tokens Get-ClusterNode -ErrorAction Stop | ForEach-Object { $nodeName = $nodeName = ${_}.Name if ($nodeName -ine $env:computername) { Copy-FileToRemoteNode -remoteNode $nodeName -source $global:accessFileLocation -destination $global:accessFileLocation } } } } } function Initialize-Node { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName "Provisioning Node $nodeName" Install-MocBinaries -nodeName $nodeName Initialize-HostOs -NodeName $nodeName } function Install-NodeAgent { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Provisioning NodeAgent $nodeName" $cloudFqdn = Get-CloudFqdn $debugArgs = "" if ($global:config[$modulename]["insecure"]) { $debugArgs += "--debug" } else { $nodeIdentity = New-MocIdentity -name $nodeName -validityDays $global:config[$moduleName]["tokenExpiryDays"] -fqdn $cloudFqdn -location $global:config[$modulename]["cloudLocation"] -port $global:config[$modulename]["cloudAgentPort"] -authport $global:config[$modulename]["cloudAgentAuthorizerPort"] New-MocRoleAssignmentWhenAvailable -identityName $nodeName -roleName "NodeContributor" -location $global:config[$modulename]["cloudLocation"] | Out-Null } return Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeAgentFullPath = $args[0] $nodeConfigLocation = $args[1] $debug = $args[2] $dotFolderPath = $args[3] $cloudagentlogin = $args[4] $nodeagentfqdn = $args[5] $svcFailureRestartMs = $args[6] $svcFailureResetSecond = $args[7] $svcNodeAgentDependency = $args[8] $nodeToCloudLoginFile = $args[9] $nodeAgentRegistryPath = $args[10] New-Item -ItemType Directory -Force -Path $nodeConfigLocation | Out-Null $acl = Get-Acl $nodeConfigLocation $acl.SetAccessRuleProtection($true,$false) $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) $acl | Set-Acl $nodeConfigLocation New-Item -ItemType File -Force -Path $nodeToCloudLoginFile | Out-Null Set-Content -Path $nodeToCloudLoginFile -Value $cloudagentlogin -ErrorVariable err $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'" if ($null -ne $service) { $service.delete() | Out-Null } Remove-Item 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application\wssdagent\' -force -ErrorAction SilentlyContinue New-Service -Name "wssdagent" -BinaryPath """$nodeAgentFullPath"" --service $debug --basedir $nodeConfigLocation --cloudloginfile ${nodeToCloudLoginFile} --dotfolderpath $dotFolderPath --nodeagentfqdn $nodeagentfqdn --objectdatastore ""registry""" -StartupType "Automatic" -DisplayName "WSSD Agent Service" | Out-Null # Allow the service to restart twice on failure. Reset the failure counter every hour. $result = sc.exe failure "wssdagent" actions= "restart/$svcFailureRestartMs/restart/$svcFailureRestartMs//0" reset= $svcFailureResetSecond if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } # Make the service dependant on WMI and VMMS services $result = sc.exe config "wssdagent" depend= $svcNodeAgentDependency if ($LASTEXITCODE -ne 0) { throw "Error " + $LASTEXITCODE + ". " + $result } Start-Service wssdagent -WarningAction:SilentlyContinue | Out-Null # Set Registry Permissions $acl = Get-Acl $nodeAgentRegistryPath $acl.SetAccessRuleProtection($true,$false) $accessRule = New-Object System.Security.AccessControl.RegistryAccessRule("BUILTIN\Administrators","FullControl","ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($accessRule) $acl | Set-Acl $nodeAgentRegistryPath } -ArgumentList $global:nodeAgentFullPath, $global:config[$modulename]["nodeConfigLocation"], $debugArgs, $global:config[$modulename]["nodeConfigLocation"], $nodeIdentity, $nodeName, $script:svcFailureRestartMs, $script:svcFailureResetSecond, $script:svcNodeAgentDependency, $global:config[$modulename]["nodeToCloudLoginYAML"], $global:nodeAgentRegistryPath } function Request-DnsReplication { <# .DESCRIPTION This optionally attempts to speed up DNS replication by manually replicating data between DNSs. In order for this method to succeed, the current user needs to have sufficient access permissions to update entries in the DNS server. In case of a failure, the code is skiped and this function is a best-effort. #> try { $forceDnsReplication = Get-ConfigurationValue -module $moduleName -type ([Type][System.Boolean]) -name "forceDnsReplication" if ($forceDnsReplication) { # Only proceed if the Get-DnsServerResourceRecord cmdlet is available (i.e. if the DNS-Server-Tools are installed) if ((Get-Module "DnsServer" -All -ErrorAction Ignore).Count -eq 0) { $wf = Get-WindowsOptionalFeature -FeatureName "DNS-Server-Tools" -Online if ($null -eq $wf) { # no-op return } if ($wf.State -ine "Enabled") { Write-SubStatus -moduleName $moduleName $(" - Installing missing feature 'DNS-Server-Tools' ...") Enable-WindowsOptionalFeature -Online -FeatureName "DNS-Server-Tools" -All -NoRestart -WarningAction SilentlyContinue } } Import-Module "DnsServer" $vnet = Get-VNetConfiguration -module $moduleName # Get the Ip associated with the cluster role and the host NIC associated with the vnetName network $ClusterResourceIP = (Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction SilentlyContinue | Get-ClusterResource -ErrorAction SilentlyContinue | Where-Object { $_.ResourceType -eq "IP Address" } | Get-ClusterParameter -name Address -ErrorAction Ignore).Value $interfaceGuid = @(Get-VMNetworkAdapter -ManagementOS -SwitchName $vnet.VswitchName -ErrorAction Ignore)[0].DeviceId if ($null -ne $ClusterResourceIP -and $null -ne $interfaceGuid) { $externalInterfaceIpConfig = Get-NetAdapter | Where-Object { $_.InterfaceGuid -ieq $interfaceGuid } | Get-NetIPConfiguration $dnsZone = $externalInterfaceIpConfig.NetProfile.Name $dnsServers = ($externalInterfaceIpConfig.DNSServer | Where-Object { $_.AddressFamily -eq 2 }).ServerAddresses foreach ($dnsServer in $dnsServers) { Write-SubStatus -moduleName $moduleName $("Forcefully registering cloudagent service on DNS server '" + $dnsServer + "'") Get-DnsServerResourceRecord -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -ErrorAction Ignore | Remove-DnsServerResourceRecord -ComputerName $dnsServer -Force -ZoneName $dnsZone Add-DnsServerResourceRecordA -ComputerName $dnsServer -Name $global:config[$modulename]["clusterRoleName"] -ZoneName $dnsZone -AllowUpdateAny -IPv4Address $ClusterResourceIP -TimeToLive 01:00:00 } } } } catch { Write-SubStatus -moduleName $moduleName $("Could not force DNS replication. Skipping...") } } #endregion #region Verification Functions function Test-CloudConfiguration { <# .DESCRIPTION A basic sanity test of cloud and all node configuration. #> if (Test-MultiNodeDeployment) { # Check for Cloud Agent Gen App Service Get-ClusterGroup $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null Get-ClusterResource -Name $global:config[$modulename]["clusterRoleName"].ToString() -ErrorAction Stop | Out-Null Get-ClusterNode -ErrorAction Stop | ForEach-Object { Test-NodeConfiguration -nodeName $_.Name } } else { Test-NodeConfiguration -nodeName ($env:computername) } Test-CloudResource } function Test-NodeConfiguration { <# .DESCRIPTION A basic sanity test to make sure that a node is ready to deploy kubernetes. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Test-ForWindowsFeatures -nodeName $nodeName $vnet = Get-VNetConfiguration -module $moduleName Test-HostNetworking -nodeName $nodeName -vswitchName $vnet.VswitchName Write-Status -moduleName $moduleName "Performing sanity checks on $nodeName" Test-MocInstallation -nodeName $nodeName Write-SubStatus -moduleName $moduleName "Testing for ssh key" if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) { throw ($global:config[$modulename]["sshPrivateKey"]) + " does not exist" } Test-Process -processName "wssdagent" -nodeName $nodeName Write-SubStatus -moduleName $moduleName "Testing if cloud node resource is provisioned" Get-MocNode -name $nodeName -location $global:config[$modulename]["cloudLocation"] | Out-Null } function Test-CloudResource { <# .DESCRIPTION A basic sanity test to ensure that cloudagent has been provisioned with the expected resources. #> Write-SubStatus -moduleName $moduleName "Testing if cloud location resource is provisioned" Get-MocLocation -name $global:config[$modulename]["cloudLocation"] | Out-Null Write-SubStatus -moduleName $moduleName "Testing if cloud container resource is provisioned" Get-MocContainer -name $global:cloudStorageContainer -location $global:config[$modulename]["cloudLocation"] | Out-Null } function Test-HostNetworking { <# .DESCRIPTION Sanity check the host networking configuration. .PARAMETER nodeName The node to execute on. .PARAMETER vswitchName The name of the vnet switch to be checked. #> param ( [parameter(Mandatory=$true)] [string]$nodeName, [parameter(Mandatory=$true)] [string]$vswitchName ) Write-Status -moduleName $moduleName "Checking host networking configuration on $nodeName" $isMultinode = Test-MultiNodeDeployment Invoke-Command -ComputerName $nodeName -ScriptBlock { $vswitchName = $args[0] $nodeName = $args[1] $isMultinode = $args[2] if ([string]::IsNullOrWhitespace($vswitchName)) { throw $("The vswitchName parameter cannot be empty") } write-verbose $(" - Checking for virtual switch with name '$vswitchName' on '$nodeName'") if($isMultinode) { $existing = Get-VMSwitch -SwitchType External -Name $vswitchName -ErrorAction SilentlyContinue if ($null -eq $existing) { throw $("The '$vswitchName' switch is missing or is not external on '$nodeName'. Please ensure that an external switch is present and pass the name of that switch as the -vswitchName parameter.") } } else { $existing = Get-VMSwitch -Name $vswitchName -ErrorAction SilentlyContinue if ($null -eq $existing) { throw $("The '$vswitchName' switch is missing on '$nodeName'. Please ensure that a switch is present and pass the name of that switch as the -vswitchName parameter.") } } return } -ArgumentList $vswitchName, $nodeName, $isMultinode } function Test-MocInstallation { <# .DESCRIPTION Sanity checks an installation to make sure that all expected binaries are present. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-SubStatus -moduleName $moduleName "Testing for expected binaries" Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath Test-Binary -nodeName $nodeName -binaryName $global:cloudAgentFullPath Test-Binary -nodeName $nodeName -binaryName $global:nodeCtlFullPath Test-Binary -nodeName $nodeName -binaryName $global:cloudCtlFullPath } #endregion #region Configuration Helper Functions function Initialize-HostOs { <# .DESCRIPTION Configures the host operating system .PARAMETER nodeName The node to execute on. #> param ( [String]$NodeName ) Test-ForWindowsFeatures -NodeName $NodeName $vnet = Get-VNetConfiguration -module $moduleName Test-HostNetworking -NodeName $NodeName -vswitchName $vnet.VswitchName Initialize-Firewall -NodeName $NodeName } function Initialize-Firewall { <# .DESCRIPTION Adds Windows Firewall exceptions to allow inbound network connections to the agents. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName "Configuring Windows Firewall on $nodeName" $firewallRules = Get-FirewallRules Invoke-Command -ComputerName $nodeName -ScriptBlock { $rules = $args[0] foreach($rule in $rules) { $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction SilentlyContinue if (!$fw) { New-NetFirewallRule -DisplayName $($rule[0]) -Protocol $($rule[1]) -LocalPort $($rule[2]) | Out-Null } } } -ArgumentList (, $firewallRules) } function Initialize-ImageDirectory { <# .DESCRIPTION Creates a directory to store images and configures the required directory permissions. .PARAMETER nodeName The node to execute on. #> param ( [String] $nodeName ) Write-SubStatus -moduleName $moduleName $("Configuring image directory permissions on $nodeName") # In order to avoid double-hop issues with network file share, we configure the # file share permission directly (instead of doing it from the remote powershell session) if ($global:config[$modulename]["imageDir"].StartsWith("\\")) { New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null $acl = get-acl $global:config[$modulename]["imageDir"] $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($AccessRule) $acl | set-acl $global:config[$modulename]["imageDir"] } else { Invoke-Command -ComputerName $nodeName -ScriptBlock { $imageDir = $args[0] New-Item -ItemType Directory -Force -Path $imageDir | Out-Null $acl = get-acl $imageDir $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow") $acl.SetAccessRule($AccessRule) $acl | set-acl $imageDir } -ArgumentList $global:config[$modulename]["imageDir"] } } function Initialize-Directories { <# .DESCRIPTION Creates work directories #> Write-Status -moduleName $moduleName "Configuring Directories" New-Item -ItemType Directory -Force -Path $global:config[$modulename]["workingDir"] | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["installationPackageDir"] | Out-Null New-Item -ItemType Directory -Force -Path $([io.Path]::Combine($global:config[$modulename]["installationPackageDir"], $global:yamlDirectoryName)) | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["imageDir"] | Out-Null New-Item -ItemType Directory -Force -Path $global:config[$modulename]["cloudConfigLocation"] | Out-Null if (-not (Test-MultiNodeDeployment)) { Initialize-ImageDirectory -nodeName ($env:computername) } if ( !(Test-Path $global:config[$modulename]["sshPrivateKey"])) { Write-SubStatus -moduleName $moduleName $("The ssh key '"+$global:config[$modulename]["sshPrivateKey"]+"' is missing. Generating it now...") New-Item -ItemType Directory -Force -Path ([IO.Path]::GetDirectoryName($global:config[$modulename]["sshPrivateKey"])) | Out-Null ssh-keygen -q -f $($global:config[$modulename]["sshPrivateKey"]) -t rsa -N '""' } } function Get-MocRelease { <# .DESCRIPTION Download the Moc release content .PARAMETER Version Version .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter(Mandatory=$true)] [String] $version, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) Get-MocReleaseContent -version $version -activity $activity if (-not ($global:config[$modulename]["useStagingShare"])) { Test-AuthenticodeBinaries -workingDir $global:config[$modulename]["installationPackageDir"] -binaries $script:mocBinaries } } function Get-MocReleaseContent { <# .DESCRIPTION Download all required files and packages for the Moc release .PARAMETER version Version of Moc .PARAMETER wssdDir Directory for wssd installation .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $version, [Parameter()] [string] $wssdDir = $global:config[$moduleName]["installationPackageDir"], [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) $wssdDir = $wssdDir -replace "\/", "\" Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Discovering Moc release content" $productRelease = Get-ProductRelease -version $version -moduleName $moduleName # find requested moc release foreach($releaseStream in $productRelease.ProductStreamRefs) { foreach($subProductRelease in $releaseStream.ProductReleases) { if ($subProductRelease.ProductName -ieq $script:productName) { $versionManifestPath = [io.Path]::Combine($wssdDir, "moc-release.json") Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $("Downloading Moc release content to $wssdDir") $downloadParams = Get-ReleaseDownloadParameters -name $subProductRelease.ProductStream -version $subProductRelease.Version -destination $wssdDir -parts 3 -moduleName $moduleName $releaseInfo = Get-DownloadSdkRelease @downloadParams if (-not ($global:config[$moduleName]["useStagingShare"])) { if ($releaseInfo.Files.Count -ne 1) { throw $("Unexpected Moc release content files downloaded. Expected 1 file, but received " + $releaseInfo.Files.Count) } $packagename = $releaseInfo.Files[0] -replace "\/", "\" # Temporary until cross-platform signing is available $auth = Get-AuthenticodeSignature -filepath $packagename if (($global:expectedAuthResponse.status -ne $auth.status) -or ($auth.SignatureType -ne $global:expectedAuthResponse.SignatureType)) { throw $("Moc release content failed authenticode verification. Expected status=$($global:expectedAuthResponse.status) and type=$($global:expectedAuthResponse.SignatureType) but received status=$($auth.status) and type=$($auth.SignatureType)") } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $("Expanding Moc package $packagename into $wssdDir") $expandoutput = expand.exe $packagename $wssdDir -f:* Write-SubStatus -moduleName $moduleName "Expand result: $expandoutput" } $versionJson = $subProductRelease | ConvertTo-Json -depth 100 set-content -path $versionManifestPath -value $versionJson -encoding UTF8 return } } } throw "Unable to get Moc release content for version $version" } function Get-MocLogs { <# .DESCRIPTION Collects all the logs from the deployment .PARAMETER Path Path to store the logs .PARAMETER activity Activity name to use when writing progress .PARAMETER VirtualMachineLogs Switch to get only the logs from the vm's (LB vm if unstacked deployment and management-cluster vm) .PARAMETER AgentLogs Switch to get only logs of the wssdagent and wssdcloudagent on all nodes .PARAMETER EventLogs Switch to get only Windows Event Logson all nodes #> [CmdletBinding()] param ( [Parameter()] [String] $path, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name, [Parameter(Mandatory=$false)] [Switch]$VirtualMachineLogs, [Parameter(Mandatory=$false)] [Switch]$AgentLogs, [Parameter(Mandatory=$false)] [Switch]$EventLogs ) Initialize-MocEnvironment -activity $activity if (!$path) { $path = [io.Path]::Combine([io.Path]::GetTempPath(), [io.Path]::GetRandomFileName()) } $allswitch = $true if ($VirtualMachineLogs.IsPresent -or $AgentLogs.IsPresent -or $EventLogs.IsPresent) { $allswitch = $false } $noswitch = (!$VirtualMachineLogs.IsPresent -and !$AgentLogs.IsPresent -and !$EventLogs.IsPresent) $logDir = [io.Path]::Combine($path, "moc") New-Item -ItemType Directory -Force -Path $logDir | Out-Null Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Collecting configuration..." $global:config[$moduleName] > $logDir"\MocConfig.txt" Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Collecting module information..." Get-Command -Module Moc | Sort-Object -Property Source > $($logDir+"\moduleinfo.txt") Write-Status -moduleName $moduleName "Collecting MOC Logs to $logDir" $isCAAvailable = Wait-ForCloudAgentEndpoint -timeout 60 -activity $activity if ($isCAAvailable) { if ($allSwitch -or $VirtualMachineLogs.IsPresent) { try { Get-GuestVirtualMachineLogs -logDirectoryName $logDir -activity $activity } catch [Exception] { Write-Status -moduleName $moduleName -msg "Exception caught!!!" Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() } } if ($allSwitch) { Invoke-MocBackup -path ([Io.Path]::Combine($logDir, "store")) } } else { Write-SubStatus -moduleName $moduleName "CloudAgent is Unreachable ... Collecting as many logs as possible" } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Gathering cloud logs..." if ($allSwitch -or $AgentLogs.IsPresent) { try { $log_cloud_agent_dir = [io.Path]::Combine($logDir, "cloudlogs") New-Item -ItemType Directory -Force -Path $log_cloud_agent_dir | Out-Null Copy-FileLocal -Source $($global:config[$moduleName]["cloudConfigLocation"]+"\log\*") -Destination $log_cloud_agent_dir } catch [Exception] { Write-Status -moduleName $moduleName -msg "Exception caught! Failed to Capture CloudAgent logs" Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() if (($global:config[$moduleName]["cloudConfigLocation"] -ieq $global:defaultCloudConfigLocation) -and (Test-MultiNodeDeployment)) { Write-SubStatus -moduleName $moduleName -msg "Please Try Setting the -cloudConfigLocation param" } } } if (Test-MultiNodeDeployment) { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Gathering cluster logs..." Get-Cluster -ErrorAction SilentlyContinue | Select-Object -Property * >> $log_cloud_agent_dir"\failover_cluster.txt" Get-ClusterGroup -ErrorAction SilentlyContinue >> $log_cloud_agent_dir"\failover_clustergroup.txt" Get-ClusterResource -ErrorAction SilentlyContinue >> $log_cloud_agent_dir"\failover_clusterresource.txt" Get-ClusterNetwork -ErrorAction SilentlyContinue | Select-Object -Property * >> $log_cloud_agent_dir"\failover_clusternetwork.txt" Get-ClusterSharedVolume -ErrorAction SilentlyContinue >> $log_cloud_agent_dir"\failover_clustercsv.txt" Get-ClusterLog -UseLocalTime -Destination $log_cloud_agent_dir | Out-Null $nodes = Get-ClusterNode -ErrorAction SilentlyContinue foreach ($node in $nodes) { try { $nodeName = $node.Name Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Gathering logs for node '$nodeName'..." $sourcesuffix = $($global:config[$moduleName]["nodeConfigLocation"]+"\log\*") $remotePath = "\\$nodeName\" + ($sourcesuffix).Replace(":", "$") $log_node_agent_dir = [io.Path]::Combine($logDir, "nodelogs_${nodeName}") New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null if ($allSwitch -or $AgentLogs.IsPresent) { Copy-FileLocal -source $remotePath -destination $log_node_agent_dir -recurse } $nodeDir = Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeAgentRegistryPath = $args[0] $parent = [System.IO.Path]::GetTempPath() [string] $guidString = [System.Guid]::NewGuid() $tempDir = [io.Path]::Combine($parent, $guidString) New-Item -ItemType Directory -Path $tempDir | Out-Null if ($allSwitch) { # Complete Registry dump Get-ItemProperty "$nodeAgentRegistryPath\*" > $tempDir"\nodeagent_registry.txt" # System Get-ComputerInfo >> $tempDir"\node_system_info.txt" Get-VM | Format-List * >> $tempDir"\node_hyperv_vm.txt" Get-VM | Get-VMNetworkAdapter | Format-List * >> $tempDir"\node_hyperv_vnic.txt" Get-VMSwitch | Format-List * >> $tempDir"\node_hyperv_vswitch.txt" try { # Expected to fail if nodeagent is not started with debug flag Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $tempDir/"nodeagent-routine.txt" -ErrorAction SilentlyContinue } catch {} try { # CloudAgent will only be active on one node, so we expect it to fail in most cases # Expected to fail if cloudagent is not started with debug flag Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $tempDir/"cloudagent-routine.txt" -ErrorAction SilentlyContinue | Out-Null } catch {} } if ($noswitch -or $EventLogs.IsPresent) { # Event Logs Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\Application.evtx" -destination $tempDir Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\System.evtx" -destination $tempDir Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\\Microsoft-Windows-Hyper-V*.evtx" -destination $tempDir } return $tempDir } -ArgumentList $global:nodeAgentRegistryPath $remotePath = "\\$nodeName\" + ($nodeDir).Replace(":", "$") Copy-FileLocal -source $remotePath"\*" -destination $log_node_agent_dir -recurse Invoke-Command -ComputerName $nodeName -ScriptBlock { Remove-Item $args[0] -Recurse } -ArgumentList $nodeDir } catch [Exception] { # An exception was thrown, write it out and exit Write-Status -moduleName $moduleName -msg "Exception caught!!!" Write-SubStatus -moduleName $moduleName -msg $_.Exception.Message.ToString() } } } else { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Gathering local node logs..." $log_node_agent_dir = [io.Path]::Combine($logDir, "nodelogs") New-Item -ItemType Directory -Force -Path $log_node_agent_dir | Out-Null if ($allSwitch -or $AgentLogs.IsPresent) { Copy-FileLocal -source $($global:config[$moduleName]["nodeConfigLocation"]+"\log\*") -destination $log_node_agent_dir -recurse } if ($allSwitch) { # Registry dump Get-ItemProperty "$global:nodeAgentRegistryPath\*" > $log_node_agent_dir"\nodeagent_registry.txt" -ErrorAction SilentlyContinue # System Get-ComputerInfo >> $log_node_agent_dir"\node_system_info.txt" Get-VM | Format-List * >> $log_node_agent_dir"\node_hyperv_vm.txt" Get-VM | Get-VMNetworkAdapter | Format-List * > $log_node_agent_dir"\node_hyperv_vnic.txt" Get-VMSwitch | Format-List * >> $log_node_agent_dir"\node_hyperv_vswitch.txt" try { # Expected to fail if nodeagent is not started with debug flag Invoke-WebRequest http://localhost:6060/debug/pprof/goroutine?debug=1 -OutFile $log_node_agent_dir/"nodeagent-routine.txt" -ErrorAction SilentlyContinue } catch {} try { # Expected to fail if cloudagent is not started with debug flag Invoke-WebRequest http://localhost:8080/debug/pprof/goroutine?debug=1 -OutFile $log_node_agent_dir/"cloudagent-routine.txt" -ErrorAction SilentlyContinue } catch {} } if ($noswitch -or $EventLogs.IsPresent) { # Event Logs Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\Application.evtx" -destination $log_node_agent_dir Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\System.evtx" -destination $log_node_agent_dir Copy-Item "$env:SystemDrive\Windows\System32\Winevt\Logs\\Microsoft-Windows-Hyper-V*.evtx" -destination $log_node_agent_dir } } } function Get-MocEventLog { <# .DESCRIPTION Gets all the event logs from AksHci Module #> Get-WinEvent -ProviderName $moduleName -ErrorAction SilentlyContinue } function Get-MocLatestVersion { <# .DESCRIPTION Get the current MOC release version .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -module $moduleName -status $("Discovering latest version") $catalog = Get-LatestCatalog -moduleName $moduleName $productRelease = $catalog.ProductStreamRefs[0].ProductReleases[0] # find latest moc stack release foreach($subProductStream in $productRelease.ProductStreamRefs) { foreach($subProductRelease in $subProductStream.ProductReleases) { if ($subProductRelease.ProductName -ieq $script:productName) { return $subProductRelease.Version } } } throw "Unable to determine MOC latest version" } function Reset-Firewall { <# .DESCRIPTION Removes Windows Firewall exceptions that were previously added for agent communication. .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status "Cleanup Windows Firewall on $nodeName" -moduleName $moduleName $firewallRules = Get-FirewallRules Invoke-Command -ComputerName $nodeName -ScriptBlock { $rules = $args[0] foreach($rule in $rules) { $fw = Get-NetFirewallRule -DisplayName $($rule[0]) -ErrorAction SilentlyContinue if ($fw) { $fw | Remove-NetFirewallRule } } } -ArgumentList (, $firewallRules) } function Reset-Host { <# .DESCRIPTION Cleans up the host by removing resources from nodeagent and cloudagent and stopping the processes. .PARAMETER removeAll Removes all artifacts creates by the deployment script. .PARAMETER skipImageDeletion This parameter skips deletion of downloaded VHD images and leaves them present in the configured image storage location. This is intended to make redeployment faster and avoid unnecessary downloads of the VHDs. .PARAMETER skipConfigDeletion This parameter skips deletion of the module configuration. .PARAMETER activity Activity name to use when writing progress #> param ( [Switch]$removeAll, [Switch]$skipImageDeletion, [Switch]$skipConfigDeletion, [String]$activity = $MyInvocation.MyCommand.Name ) Uninstall-Cloud -activity $activity if ([string]::IsNullOrWhiteSpace($global:config[$moduleName]["cloudConfigLocation"]) -or [string]::IsNullOrWhiteSpace($global:config[$moduleName]["imageDir"])) { # This case happens during Initialize Node where the cloud config location and image dir paths don't exist return } $hosts = $env:computername if (Test-MultiNodeDeployment) { $hosts = (Get-ClusterNode -ErrorAction SilentlyContinue).Name } # In order to avoid double-hop issues with network file share, we remove the # files directly (instead of doing it from the remote powershell session) if ($removeAll.IsPresent -And $global:config[$moduleName]["cloudConfigLocation"].StartsWith("\\")) { Write-SubStatus -moduleName $moduleName "Removing cloudagent directory ..." Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction SilentlyContinue } if ($removeAll.IsPresent -And $global:config[$moduleName]["imageDir"].StartsWith("\\")) { Write-SubStatus -moduleName $moduleName "Removing image directory ..." if ($skipImageDeletion.IsPresent) { Write-SubStatus -moduleName $moduleName "Downloaded images will be preserved" Get-ChildItem -Path $global:config[$moduleName]["imageDir"] -Directory -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue } else { Remove-Item -Path $global:config[$moduleName]["imageDir"] -Force -Recurse -ErrorAction SilentlyContinue } } # Before we remove workingDir, ensure that this deployment machine is not using that directory as the current working dir. Deleting it while it is the working directory # can cause unexpected behavior later on (e.g. download agent failure in redeployment) $cwd = Get-Location | Select-Object -ExpandProperty Path if ($cwd -ieq $global:config[$moduleName]["installationPackageDir"]) { Set-Location -Path .. } $inputArgs = @( $([io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], $global:yamlDirectoryName)), $global:config[$moduleName]["workingDir"], $removeAll.IsPresent, $global:config[$moduleName]["imageDir"], $skipImageDeletion.IsPresent, $global:installDirectory, $skipConfigDeletion.IsPresent ) $hosts | ForEach-Object { Write-StatusWithProgress -activity $activity -moduleName $moduleName $("Cleaning up files on " + $_) $inputArgsIter = @($_) $inputArgsIter += $inputArgs Invoke-Command -ComputerName $_ -ScriptBlock { $hostname = $args[0] $yamlLocation = $args[1] $workingDir = $args[2] $removeAll = $args[3] $imageDir = $args[4] $skipImageDeletion = $args[5] $installDir= $args[6] $skipConfigDeletion = $args[7] write-verbose " - Removing yaml on $hostname..." Remove-Item -Path $yamlLocation -Force -Recurse -ErrorAction SilentlyContinue if ($removeAll) { write-verbose " - Removing image directory on $hostname..." if ($skipImageDeletion) { write-verbose " - Downloaded images will be preserved" Get-ChildItem -Path $imageDir -Directory -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue } else { if ($imageDir) { Remove-Item -Path $imageDir -Force -Recurse -ErrorAction SilentlyContinue } } if (-not $skipConfigDeletion) { if ($workingDir) { # Keeping the working dir as part of config deletion, since config is now part of workingDir Remove-Item -Path $workingDir -Force -Recurse -ErrorAction SilentlyContinue } } write-verbose $(" - Removing all of the installation directory contents on $hostname...") Remove-Item -Path $installDir -Force -Recurse -ErrorAction SilentlyContinue } } -ArgumentList $inputArgsIter } if (-not $skipConfigDeletion.IsPresent) { Reset-Configuration -moduleName $moduleName } } function Uninstall-Cloud { <# .DESCRIPTION Deprovision an onPremise cloud .PARAMETER activity Activity name to use when updating progress #> param ( [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Deprovisioning Cloud" $deprovisionCloudResources = $true if (Test-MultiNodeDeployment) { if ($global:config[$moduleName]["clusterRoleName"]) { $cg = Get-ClusterGroup -name $global:config[$moduleName]["clusterRoleName"].ToString() -ErrorAction SilentlyContinue if ($null -eq $cg -or $cg.State -ieq "Failed") { # Skip clean-up of cloud resources if the cluster is in a failed state $deprovisionCloudResources = $false } } } if (!(Test-Path $global:cloudCtlFullPath)) { $deprovisionCloudResources = $false } # 1. Cleanup Cloud Resources if ($deprovisionCloudResources) { $mocLocation = $($global:config[$modulename]["cloudLocation"]) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud groups..." try { Reset-MocGroup -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud gallery..." try { Reset-MocGalleryImage -location $mocLocation } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud container..." try { Reset-MocContainer -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud vippool..." try { Reset-MocVipPool -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } if (Test-MultiNodeDeployment) { try { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud cluster..." Reset-MocCluster -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } } else { try { Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud node..." Reset-MocNode -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*No connection could be made because the target machine actively refused it*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud mac pools..." try { Reset-MocMacPool -location $($mocLocation) } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Cleaning up cloud locations..." try { Reset-MocLocation } catch { if (-not ($_.Exception.Message -like "*connection closed*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } } # 2. Deprovision Cloud Agents Uninstall-CloudAgent -cloudAgentName $global:cloudAgentAppName -activity $activity # 3. DeProvision Node Agent if (Test-MultiNodeDeployment) { # Try to get the nodes, but don't fail if the cluster is in a bad state. # Since we don't want Get-ClusterNode to swallow any error from the ForEach loop. # So, we have to keep the Get-ClusterNode separate from the pipe/foreach loop. $nodes = Get-ClusterNode -ErrorAction SilentlyContinue $nodes | ForEach-Object { Uninstall-Node -activity $activity -nodeName $_.Name } } else { Uninstall-Node -activity $activity -nodeName ($env:computername) } } function Uninstall-CloudAgent { <# .DESCRIPTION Deprovision a Cloud Agent 1. Stop the Service 2. Remove Cluster Service .PARAMETER cloudAgentName Cloud agent name .PARAMETER activity Activity name to use when updating progress #> param ( [String]$cloudAgentName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Deprovisioning CloudAgent '$cloudAgentName'" Stop-AllProcesses -processName "wssdcloudagent" # Remove the Cluster Gen Service if (Test-MultiNodeDeployment) { if ($global:config[$moduleName]["clusterRoleName"]) { # Try to get the groups, but don't fail if the cluster is in a bad state. # Since we don't want Get-ClusterGroup to swallow any error from the ForEach loop. # So, we have to keep the Get-ClusterGroup separate from the pipe/foreach loop. $nodes = Get-ClusterGroup $global:config[$moduleName]["clusterRoleName"].ToString() -ErrorAction SilentlyContinue $nodes | ForEach-Object { Remove-ClusterGroup -InputObject $_ -Force -RemoveResources -ErrorAction SilentlyContinue } } $nodes = Get-ClusterNode -ErrorAction SilentlyContinue $nodes.Name | ForEach-Object { Invoke-Command -ComputerName $_ -ScriptBlock { $cloudConfigLocation = $args[0] $registryLocation = $args[1] try { Get-Service wssdcloudagent -ErrorAction SilentlyContinue | ForEach-Object { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { Stop-Service wssdcloudagent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } } # Flush dns on all nodes to avoid issues with stale entries when re-installing cloudagent. # The server flushes dns during installation, so not needed for single node. Clear-DnsClientCache } catch [Exception] { # Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } write-verbose " - Removing cloudagent directory on $(hostname)..." if ($cloudConfigLocation) { Remove-Item -Path $cloudConfigLocation -Force -Recurse -ErrorAction SilentlyContinue } write-verbose " - Removing cloudagent registry on $(hostname)..." Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction SilentlyContinue } -ArgumentList @($global:config[$moduleName]["cloudConfigLocation"], $global:cloudAgentRegistryPath) } } else { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdcloudagent'" if ($null -ne $service) { Stop-Service WssdCloudAgent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } try { write-verbose " - Removing cloudagent directory on $(hostname)..." Remove-Item -Path $global:config[$moduleName]["cloudConfigLocation"] -Force -Recurse -ErrorAction SilentlyContinue write-verbose " - Removing cloudagent registry on $(hostname)..." Remove-Item -Path $global:cloudAgentRegistryPath -Recurse -Force -ErrorAction SilentlyContinue } catch { Write-ModuleEventLog -moduleName $moduleName -entryType Error -eventId 100 -message "$activity - $_" } } } function Uninstall-NodeAgent { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. #> param ( [String]$nodeName ) Write-Status -moduleName $moduleName "Deprovisioning NodeAgent $nodeName" $nodeConfigLocation = Get-ConfigurationValue -module $moduleName -name "nodeConfigLocation" if ([string]::IsNullOrWhiteSpace($nodeConfigLocation)) { # This might happen in cases where the configuration is lost. # We make best effort to cleanup the default location $nodeConfigLocation = $global:defaultNodeConfigLocation } Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeConfigLocation = $args[0] $registryLocation = $args[1] Get-Service WssdAgent -ErrorAction SilentlyContinue | ForEach-Object { $service = Get-WmiObject -Class Win32_Service -Filter "Name='wssdagent'" if ($null -ne $service) { Stop-Service WssdAgent -ErrorAction:SilentlyContinue $service.delete() | Out-Null } } write-verbose " - Removing nodeagent directory on $(hostname)..." Remove-Item -Path $nodeConfigLocation -Force -Recurse -ErrorAction SilentlyContinue write-verbose " - Removing nodeagent registry on $(hostname)..." Remove-Item -Path $registryLocation -Recurse -Force -ErrorAction SilentlyContinue } -ArgumentList @($nodeConfigLocation, $global:nodeAgentRegistryPath) } function Uninstall-Node { <# .DESCRIPTION Provision a Node Agent 1. Download Node Agent Binary to the Node 2. Register as Windows service 3. Configure Service 4. Start the Service .PARAMETER nodeName The node to execute on. .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Deprovisioning Node $nodeName" Reset-Firewall -NodeName $nodeName Uninstall-NodeAgent -nodeName $nodeName Uninstall-MocBinaries -nodeName $nodeName Invoke-Command -ComputerName $nodeName -ScriptBlock { $tmp = $args[0] write-verbose " - Removing $tmp on $(hostname)..." Remove-Item -Path $tmp -Force -Recurse -ErrorAction SilentlyContinue } -ArgumentList @($global:mocMetadataRoot) } #region moc operations #region moc macpool function Set-MocYaml { <# .DESCRIPTION Sets the content of the input to a file and return the path .PARAMETER yamlData input string #> param ( [Parameter(Mandatory=$true)] [String]$yamlData ) if ([string]::IsNullOrWhiteSpace($yamlData)) { throw "Invalid Yaml input" } $yamlFile = [io.Path]::GetTempFileName() + ".yaml" if ( $global:config -and $global:config[$moduleName] -and (-not [string]::IsNullOrWhiteSpace($global:config[$moduleName]["installationPackageDir"])) ) { $yamlFile = [io.Path]::Combine($global:config[$moduleName]["installationPackageDir"], $global:yamlDirectoryName, ([io.Path]::GetRandomFileName() + ".yaml")) } Set-Content -Path $yamlFile -Value $yamlData -ErrorVariable err if (!$err -and $err.count -gt 0) { throw $err } Write-SubStatus -moduleName $moduleName -msg "Set-MocYaml [$yamlFile] => [$yamlData]" return $yamlFile } function New-MocMacPool { <# .DESCRIPTION Adds a macpool resource to moc . .PARAMETER name name of macpool .PARAMETER location location .PARAMETER macPoolStart First MAC address in the pool .PARAMETER macPoolEnd Last MAC address in the pool #> param ( [Parameter(Mandatory=$true)] [string]$name, [Parameter(Mandatory=$true)] [string]$macPoolStart, [Parameter(Mandatory=$true)] [string]$macPoolEnd, [Parameter(Mandatory=$true)] [string]$location ) $yaml = @" name: "$name" macpoolpropertiesformat: range: startmacaddress: "$macPoolStart" endmacaddress: "$macPoolEnd" "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " network macpool create --config $yamlFile --location $location" } function Get-MocMacPool { <# .DESCRIPTION Removes a macpool resource from moc. .PARAMETER name Name of the macpool .PARAMETER location Location for the macpool #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network macpool list --location "+$location) } else { Invoke-MocShowCommand $(" network macpool show --name ""$name"" --location "+$location) } } function Remove-MocMacPool { <# .DESCRIPTION Removes a macpool resource from moc. .PARAMETER name Name of the macpool .PARAMETER location Location for the macpool #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" network macpool delete --name ""$name"" --location "+$location) } function Reset-MocMacPool { <# .DESCRIPTION Removes the macpool resource from moc. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand " network macpool list --output tsv --query ""[*].name"" --location $location" | ForEach-Object { $entityName = $_ if ($entityName -ine "Default") { Remove-MocMacPool -name $entityName -location $location } } } #endregion moc macpool #region moc location function New-MocLocation { <# .DESCRIPTION Adds a location resource to moc . .PARAMETER name Name of the location #> param ( [Parameter(Mandatory=$true)] [String]$name ) Write-Status -moduleName $moduleName "Creating cloud location ($name)" $yaml = @" name: $name "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " cloud location create --config $yamlFile" } function Get-MocLocation { <# .DESCRIPTION Removes a Location resource from moc. .PARAMETER name Name of the Location #> param ( [String]$name ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud location list") } else { Invoke-MocShowCommand $(" cloud location show --name ""$name"" ") } } function Remove-MocLocation { <# .DESCRIPTION Removes a Location resource from moc. .PARAMETER name Name of the Location #> param ( [Parameter(Mandatory=$true)] [String]$name ) Invoke-MocCommand $(" cloud location delete --name ""$name"" ") } function Reset-MocLocation { <# .DESCRIPTION Remove all moc locations. #> Invoke-MocCommand " cloud location list --output tsv --query ""[*].name""" | ForEach-Object { $entityName = $_ if ($entityName -ine "Reserved") { Remove-MocLocation -name $entityName } } } #endregion moc location #region moc group function New-MocGroup { <# .DESCRIPTION Adds a group resource to moc . .PARAMETER name Name of the group .PARAMETER location Location for the group #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) $yaml = @" name: $name location: $location "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " cloud group create --config $yamlFile --location $location" } function Get-MocGroup { <# .DESCRIPTION Removes a Group resource from moc. .PARAMETER name Name of the Group .PARAMETER location Location for the Group #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud group list --location $location") } else { Invoke-MocShowCommand $(" cloud group show --name ""$name"" --location $location") } } function Remove-MocGroup { <# .DESCRIPTION Removes a Group resource from moc. .PARAMETER name Name of the Group .PARAMETER location Location for the Group #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud group delete --name ""$name"" --location $location") } function Reset-MocGroup { <# .DESCRIPTION Remove all moc groups. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud group list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocGroup -name $entityName -location $location } } } #endregion moc group #region moc container function New-MocContainer { <# .DESCRIPTION Adds a container resource to moc . .PARAMETER name Name of the container .PARAMETER path path to create the container .PARAMETER location Location of the container #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$path, [Parameter(Mandatory=$true)] [String]$location ) $yaml = @" name: $name containerproperties: path: $path "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " storage container create --config $yamlFile --location $location" } function Get-MocContainer { <# .DESCRIPTION Removes a Container resource from moc. .PARAMETER name Name of the Container .PARAMETER location Location for the Container #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" storage container list --location $location") } else { Invoke-MocShowCommand $(" storage container show --name ""$name"" --location $location") } } function Remove-MocContainer { <# .DESCRIPTION Removes a Container resource from moc. .PARAMETER name Name of the Container .PARAMETER location Location for the Container #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" storage container delete --name ""$name"" --location "+$location) } function Reset-MocContainer { <# .DESCRIPTION Remove all moc storage containers. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" storage container list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Invoke-MocCommand $(" storage container delete --name ""$entityName"" --location $location") } } } #endregion moc container #region moc vippool function New-MocVipPool { <# .DESCRIPTION Adds a vip pool resource to moc . .PARAMETER name name of vippool .PARAMETER location location .PARAMETER vipPoolStart First vip address in the pool .PARAMETER vipPoolEnd Last vip address in the pool #> param ( [Parameter(Mandatory=$true)] [string]$name, [Parameter(Mandatory=$true)] [string]$vipPoolStart, [Parameter(Mandatory=$true)] [string]$vipPoolEnd, [Parameter(Mandatory=$true)] [string]$location ) $yaml = @" name: "$name" vippoolpropertiesformat: startip: "$vipPoolStart" endip: "$vipPoolEnd" "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " network vippool create --config $yamlFile --location $location" } function Get-MocVipPool { <# .DESCRIPTION Get a VipPool resource from moc. .PARAMETER name Name of the VipPool .PARAMETER location Location for the VipPool #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network vippool list --location "+$location) } else { Invoke-MocShowCommand $(" network vippool show --name ""$name"" --location "+$location) } } function Remove-MocVipPool { <# .DESCRIPTION Removes a VipPool resource from moc. .PARAMETER name Name of the VipPool .PARAMETER location Location for the VipPool #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" network vippool delete --name ""$name"" --location "+$location) } function Reset-MocVipPool { <# .DESCRIPTION Remove all moc network vippools. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" network vippool list --output tsv --query ""[*].name"" --location "+$location) | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Invoke-MocCommand $(" network vippool delete --name ""$entityName"" --location "+$location) } } } #endregion moc vippool #region moc node function New-MocNode { <# .DESCRIPTION Adds a node resource to moc . .PARAMETER name Name of the node .PARAMETER location Location for the node .PARAMETER fqdn fqdn of the node #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$fqdn ) $yaml = @" name: $name location: $location nodeproperties: fqdn: "$fqdn" port: 45000 authorizerport: 45001 "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " cloud node create --config $yamlFile --location $location" } function Get-MocNode { <# .DESCRIPTION Get a Node(s) resource from moc. .PARAMETER name Name of the Node .PARAMETER location Location for the Node #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud node list --location $location") } else { Invoke-MocShowCommand $(" cloud node show --name ""$name"" --location $location") } } function Remove-MocNode { <# .DESCRIPTION Removes a Node resource from moc. .PARAMETER name Name of the Node .PARAMETER location Location for the Node #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud node delete --name ""$name"" --location $location") } function Reset-MocNode { <# .DESCRIPTION Remove all moc nodes. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud node list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocNode -name $entityName -location $location } } } #endregion moc node #region moc physical node function Confirm-NodeStatus { <# .DESCRIPTION sanity check before New-MocPhysicalNode and Remove-MocPhysicalNode .PARAMETER nodeName The name of the node in the FC .PARAMETER checkConfiguration To check if the node is configured and running #> param ( [String]$nodeName, [switch]$checkConfiguration ) # check if node in FC $node = Get-ClusterNode -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $nodeName -and $_.State -ieq 'Up'} if (! ($null -eq $node)) { throw ("Node $nodeName is not in the Failover Cluster") } if (!($checkConfiguration.IsPresent)) { return } # check if node is configured Test-Binary -nodeName $nodeName -binaryName $global:nodeAgentFullPath Test-NodeConfiguration -nodeName $nodeName } function New-MocPhysicalNode { <# .DESCRIPTION FRU scenario, orchestrates the onboarding of a new node .PARAMETER nodeName The name of the node in the FC .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) if (!(Test-MultiNodeDeployment)) { throw ("Adding new node supported only for multinode deployments") } Confirm-NodeStatus -nodeName $nodeName # node setup Initialize-Node -nodeName $nodeName $nodeLoginYaml = $(Get-MocConfigValue -name "nodeLoginYAML") Install-NodeAgent -nodeName $nodeName -activity $activity Invoke-NodeLogin -nodeName $nodeName -loginYaml $nodeLoginYaml # check if agent is installed successfully and registered with CA Confirm-NodeStatus -nodeName $nodeName -checkConfiguration # wait for all nodes to become active $mocLocation = $global:config[$modulename]["cloudLocation"] $areNodesActive = Wait-ForActiveNodes -location $mocLocation -activity $activity if (!($areNodesActive)) { throw $("Nodes have not reached Active state") } } function Remove-MocPhysicalNode { <# .DESCRIPTION FRU scenario, orchestrates the offboarding of a node that has failed .PARAMETER nodeName The name of the node in the FC .PARAMETER activity Activity name to use when updating progress #> param ( [String]$nodeName, [String]$activity = $MyInvocation.MyCommand.Name ) if (!(Test-MultiNodeDeployment)) { throw ("Removing node supported only for multinode deployments") } try { Uninstall-Node -nodeName $nodeName -activity $activity } catch {} # swallow error if node is unreachable and uninstall is not successful Remove-MocIdentity -name $nodeName # check if resources have migrated $mocLocation = $($global:config[$modulename]["cloudLocation"]) Remove-MocNode -name $nodeName -location $mocLocation } #endregion moc physical node #region moc cluster function New-MocCluster { <# .DESCRIPTION Adds a cluster resource to moc . .PARAMETER name Name of the cluster .PARAMETER location Location for the cluster .PARAMETER fqdn Fqdn of the cluster #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$fqdn ) $yaml = @" name: $name location: $location clusterproperties: fqdn: "$fqdn" "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " cloud cluster create --config $yamlFile --location $location" } function Get-MocCluster { <# .DESCRIPTION Get a Cluster(s) resource from moc. .PARAMETER name Name of the Cluster .PARAMETER location Location for the Cluster #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" cloud cluster list --location "+$location) } else { Invoke-MocShowCommand $(" cloud cluster show --name ""$name"" --location "+$location) } } function Remove-MocCluster { <# .DESCRIPTION Removes a Cluster resource from moc. .PARAMETER name Name of the Cluster .PARAMETER location Location for the Cluster #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud cluster delete --name ""$name"" --location "+$location) } function Reset-MocCluster { <# .DESCRIPTION Remove all moc clusters. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" cloud cluster list --output tsv --query ""[*].name"" --location $location") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocCluster -name $entityName -location $location } } } #endregion moc cluster #region moc galleryimage function New-MocGalleryImage { <# .DESCRIPTION Adds a galleryimage resource to cloudagent. .PARAMETER name Name of the galleryimage. .PARAMETER location Location for the galleryimage. .PARAMETER imagePath Hostname or IP of the galleryimage. .PARAMETER container Port that galleryimageagent is listening on. #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location, [Parameter(Mandatory=$true)] [String]$container, [Parameter(Mandatory=$true)] [System.IO.FileInfo]$imagePath ) $yaml = @" name: $name properties: containername: $container "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " compute galleryimage create --config $yamlFile --location $location --image-path $imagePath" } function Get-MocGalleryImage { <# .DESCRIPTION Removes a GalleryImage resource from cloudagent. .PARAMETER name Name of the GalleryImage .PARAMETER location Location for the GalleryImage #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$location ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute galleryimage list --location $location") } else { Invoke-MocShowCommand $(" compute galleryimage show --name ""$name"" --location $location") } } function Remove-MocGalleryImage { <# .DESCRIPTION Removes a GalleryImage resource from cloudagent. .PARAMETER name Name of the GalleryImage .PARAMETER location Location for the GalleryImage #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" compute galleryimage delete --name ""$name"" --location "+$location) } function Reset-MocGalleryImage { <# .DESCRIPTION Remove all compute galleryimages. .PARAMETER location The location to reset the entity #> param ( [Parameter(Mandatory=$true)] [String]$location ) Invoke-MocCommand $(" compute galleryimage list --output tsv --query ""[*].name"" --location $location ") | ForEach-Object { $entityName = $_ if ($entityName -ine "No GalleryImage Resources") { Remove-MocGalleryImage -name $entityName -location $location } } } #endregion moc galleryimage #region moc network interface function New-MocNetworkInterface { <# .DESCRIPTION Adds a vnic resource to moc . .PARAMETER name Name of the vnic .PARAMETER virtualNetworkName The vnet that the vnic should be connected to .PARAMETER group The name of the group in which the vnic resides .PARAMETER ipAddress The ipAddress of the networkInterface .PARAMETER loadBalancerBackend The loadBalancer Backend to which this network interface should be associated to .PARAMETER macAddress The macAddress of the networkInterface #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$virtualNetworkName, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [String]$ipAddress, [Parameter(Mandatory=$false)] [String]$loadBalancerBackend, [Parameter(Mandatory=$false)] [String]$macAddress ) $yaml = @" name: $name interfacepropertiesformat: ipconfigurations: - interfaceipconfigurationpropertiesformat: subnet: id: "$virtualNetworkName" primary: true "@ if ($macAddress) { $yaml += "`n" + @" macaddress: $macAddress "@ } if ($ipAddress) { $yaml += "`n" + @" privateipaddress: $ipAddress "@ } if ($loadBalancerBackend) { $yaml += "`n" + @" loadbalancerbackendaddresspools: - backendaddresspoolpropertiesformat: name: $loadBalancerBackend "@ } $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " network vnic create --config $yamlFile --group $group" } function Get-MocNetworkInterface { <# .DESCRIPTION Removes a NetworkInterface resource from moc. .PARAMETER name Name of the NetworkInterface .PARAMETER group group for the NetworkInterface #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network vnic list --group $group") } else { Invoke-MocShowCommand $(" network vnic show --name ""$name"" --group $group") } } function Remove-MocNetworkInterface { <# .DESCRIPTION Removes a NetworkInterface resource from moc. .PARAMETER name Name of the NetworkInterface .PARAMETER group group for the NetworkInterface #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network vnic delete --name ""$name"" --group "+$group) } function Reset-MocNetworkInterface { <# .DESCRIPTION Remove all moc networkinterface. .PARAMETER group group for the NetworkInterface #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network vnic list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocNetworkInterface -name $entityName -group $group } } } #endregion moc network interface #region moc loadbalancer function New-MocLoadBalancer { <# .DESCRIPTION Adds a loadbalancer resource to moc . .PARAMETER name Name of the loadbalancer .PARAMETER virtualNetworkName The vnet that the loadbalancer should be connected to .PARAMETER group The name of the group in which the loadbalancer resides .PARAMETER backendPoolName The backendPoolName .PARAMETER frontendPort The frontendPort for the loadbalancer .PARAMETER backendPort The backendPort for the loadbalancer .PARAMETER protocol The protocol for the loadbalancer #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$virtualNetworkName, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$backendPoolName, [Parameter(Mandatory=$true)] [int]$frontendPort, [Parameter(Mandatory=$true)] [int]$backendPort, [Parameter(Mandatory=$true)] [ValidateSet('TCP', 'UDP')] [string]$protocol ) $yaml = @" name: $name loadbalancerpropertiesformat: backendaddresspools: - name: $backendPoolName frontendipconfigurations: - frontendipconfigurationpropertiesformat: subnet: id: $virtualNetworkName loadbalancingrules: - loadbalancingrulepropertiesformat: frontendport: $frontendPort backendport: $backendPort protocol: $protocol "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " network loadbalancer create --config $yamlFile --group $group" } function Get-MocLoadBalancer { <# .DESCRIPTION Get a LoadBalancer resource from moc. .PARAMETER name Name of the LoadBalancer .PARAMETER group group for the LoadBalancer #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network loadbalancer list --group "+$group) } else { Invoke-MocShowCommand $(" network loadbalancer show --name ""$name"" --group "+$group) } } function Remove-MocLoadBalancer { <# .DESCRIPTION Removes a LoadBalancer resource from moc. .PARAMETER name Name of the LoadBalancer .PARAMETER group group for the LoadBalancer #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network loadbalancer delete --name ""$name"" --group "+$group) } function Reset-MocLoadBalancer { <# .DESCRIPTION Remove all moc loadbalancer. .PARAMETER group group for the LoadBalancer #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network loadbalancer list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocLoadBalancer -name $entityName -group $group } } } #endregion moc network interface #region moc virtual network function New-MocIPPool { <# .DESCRIPTION Adds a vnet resource to moc . .PARAMETER name Name of the vnet .PARAMETER type Type of the vnet .PARAMETER startIpAddress The name of the group in which the vnet resides .PARAMETER endIPAddress name of the macpool #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [ValidateSet('vm', 'vippool')] [string]$type, [Parameter(Mandatory=$false)] [string]$startIpAddress, [Parameter(Mandatory=$false)] [string]$endIPAddress ) return @" - name: $name type: $type start: $startIpAddress end: $endIPAddress "@ } function New-MocVirtualNetwork { <# .DESCRIPTION Adds a vnet resource to moc . .PARAMETER name Name of the vnet .PARAMETER type Type of the vnet .PARAMETER group The name of the group in which the vnet resides .PARAMETER macPool name of the macpool .PARAMETER ipPools list of IPPools .PARAMETER vlanID Non Zero value, if vlan is required .PARAMETER tags Additional tags for vnet creation #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$false)] [string]$type, [Parameter(Mandatory=$false)] [string]$macPool, [Parameter(Mandatory=$false)] [string[]]$ipPools, [Parameter(Mandatory=$false)] [uint32]$vlanID, [Parameter(Mandatory=$false)] [Hashtable]$tags ) if ([string]::IsNullOrWhiteSpace($type)) { $type = "Transparent" if ($name -eq "Default Switch") { $type = "ICS" } } $yaml = @" name: "$name" type: "$type" "@ if ((-not [string]::IsNullOrWhiteSpace($macPool)) -or ($vlanID -gt 0) -or ($ipPools -and $ipPools.Count -gt 0)) { $yaml += "`n" + @" virtualnetworkpropertiesformat: "@ } if (-not [string]::IsNullOrWhiteSpace($macPool)) { $yaml += "`n" + @" macpoolname: "$macPool" "@ } if (($vlanID -gt 0) -or ($ipPools -and $ipPools.Count -gt 0)) { $yaml += "`n" + @" subnets: - name: test subnetpropertiesformat: "@ } if ($vlanID -gt 0) { $yaml += "`n" + @" vlan: $vlanID "@ } if ($ipPools -and $ipPools.Count -gt 0) { $yaml += "`n" + @" ipallocation: dynamic ippools: "@ $ipPools | ForEach-Object { $yaml += "`n" + $_ } } if ($tags -and $tags.Count -gt 0) { $yaml += "`n" + @" tags: "@ foreach ($item in $tags.Keys) { $val = $tags[$item] $yaml += "`n" + @" $item`: $val "@ } } $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " network vnet create --config $yamlFile --group $group" } function Get-MocVirtualNetwork { <# .DESCRIPTION Get a VirtualNetwork resource from moc. .PARAMETER name Name of the VirtualNetwork .PARAMETER group group for the VirtualNetwork #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" network virtualnetwork list --group "+$group) } else { Invoke-MocShowCommand $(" network virtualnetwork show --name ""$name"" --group "+$group) } } function Remove-MocVirtualNetwork { <# .DESCRIPTION Removes a VirtualNetwork resource from moc. .PARAMETER name Name of the VirtualNetwork .PARAMETER group group for the VirtualNetwork #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network virtualnetwork delete --name ""$name"" --group $group") } function Reset-MocVirtualNetwork { <# .DESCRIPTION Remove all moc virtualnetwork. .PARAMETER group group for the virtualnetwork #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" network virtualnetwork list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVirtualNetwork -name $entityName -group $group } } } #endregion moc network interface #region moc virtualmachine function New-MocVirtualMachine { <# .DESCRIPTION Create a moc virtualmachine .PARAMETER name Name of the virtualmachine .PARAMETER group The name of the group in which the virtualmachine resides .PARAMETER galleryImageName The name of the galleryImageName to use for image .PARAMETER osType The type of operating system .PARAMETER networkInterfaces List of networkinterfaces to connect the virtualmachine .PARAMETER virtualHardDisks List of virtualHardDisks to connect the virtualmachine .PARAMETER storageContainerName The name of the storageContainerName to store the virtualmachine data .PARAMETER disableHA switch to disable high availability .PARAMETER computerName name of the computer to set on the VM. .PARAMETER adminUser administrator username .PARAMETER adminPass administrator Password in Clear text .PARAMETER bootstrapType bootstrap type [CloudInit/WindowsAnswerFile] .PARAMETER tags hashtable of tags #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$galleryImageName, [Parameter(Mandatory=$true)] [ValidateSet('Windows', 'Linux')] [string]$osType, [Parameter(Mandatory=$false)] [string[]]$networkInterfaces, [Parameter(Mandatory=$false)] [string[]]$virtualHardDisks, [Parameter(Mandatory=$false)] [string]$storageContainerName = "", [Parameter(Mandatory=$false)] [switch]$disableHA, [Parameter(Mandatory=$true)] [string]$computerName, [Parameter(Mandatory=$true)] [string]$adminUser, [Parameter(Mandatory=$true)] [string]$adminPass, [Parameter(Mandatory=$true)] [ValidateSet('CloudInit', 'WindowsAnswerFiles')] [string]$bootstrapType, [Parameter(Mandatory=$false)] [Hashtable]$tags ) $yaml = @" name: $name virtualmachineproperties: osprofile: computername: "$computerName" adminusername: "$adminUser" adminpassword: "$adminPass" customdata: "" osbootstrapengine: $bootstrapType "@ # Do not change the order of yaml. It would modify the object # Construct OsProfile related Info switch ($osType) { 'Windows' { $yaml += "`n" + @" windowsconfiguration: enableautomaticupdates: true "@ } 'Linux' { $yaml += "`n" + @" linuxconfiguration: disablepasswordauthentication: true "@ } } # Construct network profile related Info if ($networkInterfaces -and $networkInterfaces.Count -gt 0) { $yaml += "`n" + @" networkprofile: networkinterfaces: "@ $networkInterfaces | ForEach-Object { $yaml += "`n" + @" - id: $_ "@ } } # Construct storage profile related Info $yaml += "`n" + @" storageprofile: imagereference: name: $galleryImageName "@ if ($virtualHardDisks -and $virtualHardDisks.Count -gt 0) { $yaml += "`n" + @" datadisks: "@ $virtualHardDisks | ForEach-Object { $yaml += "`n" + @" - vhd: uri: $_ "@ } } if (-not [string]::IsNullOrEmpty($storageContainerName)) { $yaml += "`n" + @" vmconfigcontainername: $storageContainerName "@ } if ($disableHA.IsPresent) { $yaml += "`n" + @" disablehighavailability: true "@ } if ($tags -and $tags.Count -gt 0) { $yaml += "`n" + @" tags: "@ foreach ($item in $tags.Keys) { $val = $tags[$item] $yaml += "`n" + @" $item`: $val "@ } } $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " compute vm create --config $yamlFile --group $group" } function Get-MocVirtualMachine { <# .DESCRIPTION Get VirtualMachine(s) from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute vm list --group "+$group) } else { Invoke-MocShowCommand $(" compute vm show --name ""$name"" --group "+$group) } } function Remove-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm delete --name ""$name"" --group "+$group) } function Start-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm start --name ""$name"" --group "+$group) } function Stop-MocVirtualMachine { <# .DESCRIPTION Removes a VirtualMachine resource from moc. .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm stop --name ""$name"" --group "+$group) } function Restart-MocVirtualMachine { <# .DESCRIPTION Restart a VirtualMachine .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm restart --name ""$name"" --group "+$group) } function Get-MocVirtualMachineSizes { <# .DESCRIPTION List sizes for a moc VirtualMachine #> Invoke-MocCommand $(" compute vm list-sizes" ) } function Resize-MocVirtualMachine { <# .DESCRIPTION Resize a VirtualMachine .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER newSize newSize for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$newSize ) Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size $newSize" ) } function Resize-MocVirtualMachineCustom { <# .DESCRIPTION Resize a VirtualMachine with Custom Size .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER cpuCount new cpuCount for the VirtualMachine .PARAMETER memoryMB new memoryMB for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int]$cpuCount, [Parameter(Mandatory=$true)] [int]$memoryMB ) Invoke-MocCommand $(" compute vm resize --name ""$name"" --group $group --size Custom --cpucount $cpuCount --memorymb $memoryMB" ) } function Connect-MocVirtualHardDisk { <# .DESCRIPTION Connect a VirtualMachine to VirtualHardDisk .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName ) Invoke-MocCommand $(" compute vm disk attach --name ""$name"" --vm-name $virtualMachineName --group "+$group) } function Disconnect-MocVirtualHardDisk { <# .DESCRIPTION Disconnect a VirtualMachine from VirtualHardDisk .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName ) Invoke-MocCommand $(" compute vm disk detach --name ""$name"" --vm-name $virtualMachineName --group "+$group) } function Connect-MocNetworkInterface { <# .DESCRIPTION Connect a VirtualMachine to NetworkInterface .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName ) Invoke-MocCommand $(" compute vm nic add --name ""$name"" --vm-name $virtualMachineName --group "+$group) } function Disconnect-MocNetworkInterface { <# .DESCRIPTION Disconnect a VirtualMachine from NetworkInterface .PARAMETER name Name of the VirtualMachine .PARAMETER group group for the VirtualMachine .PARAMETER virtualMachineName virtualMachineName #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [String]$virtualMachineName ) Invoke-MocCommand $(" compute vm nic remove --name ""$name"" --vm-name $virtualMachineName --group "+$group) } function Reset-MocVirtualMachine { <# .DESCRIPTION Remove all moc virtual machines. .PARAMETER group group for the VirtualMachine #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vm list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVirtualMachine -name $entityName -group $group } } } #endregion moc virtualmachine #region moc virtualmachine function New-MocVMSS { <# .DESCRIPTION Adds a vmss resource to moc . .PARAMETER name Name of the vmss .PARAMETER galleryImageName name of the image to use for vmss .PARAMETER replicaCount replica count of vmss .PARAMETER osType OperatingSystem type of the vmss .PARAMETER group The name of the group in which the vmss resides .PARAMETER virtualNetworkName The name of the virtualNetworkName to connect the vmss to .PARAMETER storageContainerName The name of the storageContainerName to store the vmss data .PARAMETER disableHA switch to disable High availability .PARAMETER computerNamePrefix name of the computer to set on the VM. .PARAMETER adminUser administrator username .PARAMETER adminPass administrator Password in Clear text .PARAMETER sshPublicKey sshPublicKey to use to set certificate based ssh auth .PARAMETER bootstrapType bootstrap type [CloudInit/WindowsAnswerFile] .PARAMETER tags hashtable of tags #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$galleryImageName, [Parameter(Mandatory=$true)] [int]$replicaCount, [Parameter(Mandatory=$true)] [ValidateSet('Windows', 'Linux')] [string]$osType, [Parameter(Mandatory=$false)] [string]$virtualNetworkName, [Parameter(Mandatory=$false)] [string]$storageContainerName = "", [Parameter(Mandatory=$false)] [switch]$disableHA, [Parameter(Mandatory=$true)] [string]$computerNamePrefix, [Parameter(Mandatory=$true)] [string]$adminUser, [Parameter(Mandatory=$true)] [string]$adminPass, [Parameter(Mandatory=$false)] [string]$sshPublicKey, [Parameter(Mandatory=$true)] [ValidateSet('CloudInit', 'WindowsAnswerFiles')] [string]$bootstrapType, [Parameter(Mandatory=$false)] [Hashtable]$tags ) $yaml = @" name: $name sku: name: "linux" capacity: $replicaCount virtualmachinescalesetproperties: virtualmachineprofile: osprofile: computernameprefix: "$computerNamePrefix" adminusername: "$adminUser" adminpassword: "$adminPass" customdata: "" osbootstrapengine: $bootstrapType "@ $sshPublicYaml = "" # Do not change the order of yaml. It would modify the object if (-Not [string]::IsNullOrEmpty($sshPublicKey)) { $sshPublicYaml += "`n" + @" ssh: publickeys: - keydata: $sshPublicKey "@ } # Construct OsProfile related Info switch ($osType) { 'Windows' { $yaml += "`n" + @" windowsconfiguration: enableautomaticupdates: true "@ } 'Linux' { $yaml += "`n" + @" linuxconfiguration: disablepasswordauthentication: true "@ } } $yaml += $sshPublicYaml # Construct network profile related Info $yaml += "`n" + @" networkprofile: networkinterfaceconfigurations: - virtualmachinescalesetnetworkconfigurationproperties: primary: true ipconfigurations: - virtualmachinescalesetipconfigurationproperties: primary: true subnet: id: $virtualNetworkName "@ # Construct storage profile related Info $yaml += "`n" + @" storageprofile: imagereference: name: $galleryImageName "@ if (-not [string]::IsNullOrEmpty($storageContainerName)) { $yaml += "`n" + @" vmssconfigcontainername: $storageContainerName "@ } if ($disableHA.IsPresent) { $yaml += "`n" + @" disablehighavailability: true "@ } $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " compute vmss create --config $yamlFile --group $group" } function Get-MocVMSS { <# .DESCRIPTION Get a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" compute vmss list --group "+$group) } else { Invoke-MocShowCommand $(" compute vmss show --name ""$name"" --group "+$group) } } function Set-MocVMSS { <# .DESCRIPTION Get a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS .PARAMETER replicaCount replicaCount for the VMSS #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [int]$replicaCount ) Invoke-MocCommand $(" compute vmss scale --count $replicaCount --name ""$name"" --group $group" ) } function Remove-MocVMSS { <# .DESCRIPTION Removes a VMSS resource from moc. .PARAMETER name Name of the VMSS .PARAMETER group group for the VMSS #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vmss delete --name ""$name"" --group "+$group) } function Reset-MocVMSS { <# .DESCRIPTION Remove all moc vmss .PARAMETER group group for the VMSS #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" compute vmss list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVMSS -name $entityName -group $group } } } #endregion moc virtualmachine #region moc virtual harddisk function New-MocVirtualHardDisk { <# .DESCRIPTION Adds a virtualharddisk resource to moc . .PARAMETER name Name of the virtualharddisk .PARAMETER sizeBytes size in bytes for the virtualharddisk .PARAMETER group The name of the group in which the virtualharddisk resides .PARAMETER dynamic flag to specify if we want * dynamic expanding disk -> overprovisioning * static disks .PARAMETER containerName name of the storage container to use #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int64]$sizeBytes, [Parameter(Mandatory=$false)] [bool]$dynamic = $true, [Parameter(Mandatory=$false)] [string]$containerName ) $yaml = @" name: $name virtualharddiskproperties: disksizebytes: $sizeBytes virtualharddisktype: DATADISK_VIRTUALHARDDISK "@ if ($dynamic) { $yaml += "`n" + @" dynamic: true "@ } else { $yaml += "`n" + @" dynamic: false "@ } $yamlFile = Set-MocYaml -yamlData $yaml $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand " storage vhd create --config $yamlFile --group $group $cmd" } function Import-MocVirtualHardDisk { <# .DESCRIPTION Impoirts an existing virtualharddisk resource to moc . .PARAMETER name Name of the virtualharddisk .PARAMETER sourcePath sourcePath of the virtualharddisk to import .PARAMETER group The name of the group in which the virtualharddisk resides .PARAMETER containerName name of the storage container to use #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$sourcePath, [Parameter(Mandatory=$false)] [string]$cointainerName ) $yaml = @" name: $name virtualharddiskproperties: source: $sourcePath "@ $yamlFile = Set-MocYaml -yamlData $yaml $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand " storage vhd create --config $yamlFile --group $group $cmd" } function Get-MocVirtualHardDisk { <# .DESCRIPTION Get a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk .PARAMETER containerName name of the storage container to use #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$false)] [String]$containerName ) $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" storage vhd list --group "+$group + $cmd) } else { Invoke-MocShowCommand $(" storage vhd show --name ""$name"" --group "+$group + $cmd) } } function Resize-MocVirtualHardDisk { <# .DESCRIPTION Resize a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk .PARAMETER newSizeBytes newSizeBytes for the VirtualHardDisk .PARAMETER containerName name of the storage container to use #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [int64]$newSizeBytes, [Parameter(Mandatory=$true)] [string]$containerName ) $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand $( "storage vhd resize --name $name --size-bytes $newSizeBytes --group $group $cmd") } function Remove-MocVirtualHardDisk { <# .DESCRIPTION Removes a VirtualHardDisk resource from moc. .PARAMETER name Name of the VirtualHardDisk .PARAMETER group group for the VirtualHardDisk #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$containerName ) Invoke-MocCommand $(" storage vhd delete --name ""$name"" --group $group --container $containerName") } function Reset-MocVirtualHardDisk { <# .DESCRIPTION Remove all moc virtual harddisks. .PARAMETER group group for the VirtualHardDisk #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$containerName ) $cmd = "" if (-Not [string]::IsNullOrWhiteSpace($containerName)) { $cmd += " --container $containerName" } Invoke-MocCommand $(" storage vhd list --output tsv --query ""[*].name"" --group $group $cmd") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocVirtualHardDisk -name $entityName -group $group -containerName $containerName } } } #endregion moc virtual harddisk #region moc identity function New-MocIdentity { <# .DESCRIPTION Adds a identity resource to moc . .PARAMETER name Name of the identity identity. .PARAMETER validityDays Time before expiry of token for identity in days. .PARAMETER location Location for the identity. .PARAMETER fqdn Hostname or IP of the cloud agent. .PARAMETER clienttype Type of client for which identity is created. .PARAMETER port Port that cloud agent is listening on. .PARAMETER authport Authorizer Port that cloud agent is listening on. .PARAMETER encode output to be base64 encoded. #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$validityDays, [String]$fqdn = "localhost", [Parameter(Mandatory=$true)] [String]$location, [string]$clienttype = "Node", [int]$port = $global:defaultCloudAgentPort, [int]$authport = $global:defaultCloudAuthorizerPort, [switch]$encode ) Write-Status -moduleName $moduleName $("Creating identity for '$name'") $yaml = @" name: $name tokenexpiry: $validityDays location: $location identityproperties: cloudfqdn: "$fqdn" cloudport: $port cloudauthport: $authport clienttype: "$clienttype" "@ $yamlFile = Set-MocYaml -yamlData $yaml return Invoke-MocCommand " security identity create --config $yamlFile --encode=$encode" } function Remove-MocIdentity { <# .DESCRIPTION Deletes a identity resource from moc . .PARAMETER name Name of the identity identity. #> param ( [Parameter(Mandatory=$true)] [String]$name ) Write-Status -moduleName $moduleName "Deleting identity" return Invoke-MocCommand " security identity delete --name $name" } function Invoke-MocIdentityRotate { <# .DESCRIPTION Adds a identity resource to moc . .PARAMETER name Name of the identity identity. .PARAMETER encode output to be base64 encoded. #> param ( [Parameter(Mandatory=$true)] [String]$name, [switch]$encode ) Write-Status -moduleName $moduleName $("Rotating identity tokens for '$name''") return Invoke-MocCommand " security identity rotate --name $name --encode=$encode" } #endregion moc identity #region moc role assignment function New-MocRoleAssignment { <# .DESCRIPTION Assigns a moc role to a moc identity .PARAMETER identityName Name of the identity. .PARAMETER roleName Name of the role to assign. .PARAMETER location Location to which the role is scoped to. .PARAMETER group Group to which the role is scoped to. .PARAMETER providerType ProviderType to which the role is scoped to. .PARAMETER resourceName Name of resource to which the role is scoped to. #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName ) Write-Status -moduleName $moduleName "Creating moc role assignment" $cmd = "" if (-not [string]::IsNullOrWhiteSpace($location)) { $cmd += " --location $location" } if (-not [string]::IsNullOrWhiteSpace($group)) { $cmd += " --group $group" } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $cmd += " --provider-type $providerType" } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $cmd += " --resource $resourceName" } return Invoke-MocCommand " security roleassignment create --identity $identityName --role $roleName $cmd" } function New-MocRoleAssignmentWhenAvailable { <# .DESCRIPTION Waits for a moc role to be available, then assigns it to a moc identity .PARAMETER identityName Name of the identity. .PARAMETER roleName Name of the role to assign. .PARAMETER location Location to which the role is scoped to. .PARAMETER group Group to which the role is scoped to. .PARAMETER providerType ProviderType to which the role is scoped to. .PARAMETER resourceName Name of resource to which the role is scoped to. #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName ) Write-Status -moduleName $moduleName "Creating moc role assignment" $isRoleAvailable = Wait-ForMocRole -roleName $roleName if (-not $isRoleAvailable) { throw $("MOC Role $roleName is unavailable") } return New-MocRoleAssignment -identityName $identityName -roleName $roleName -location $location -group $group -providerType $providerType -resourceName $resourceName } function Get-MocRoleAssignment { <# .DESCRIPTION Get a RoleAssignment resource from moc. .PARAMETER name Name of the RoleAssignment. #> param ( [String]$name, [String]$identityName, [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName ) if ([string]::IsNullOrWhiteSpace($name)) { $cmd = "" if (-not [string]::IsNullOrWhiteSpace($identityName)) { $cmd += " --identity $identityName" } if (-not [string]::IsNullOrWhiteSpace($roleName)) { $cmd += " --role $roleName" } if (-not [string]::IsNullOrWhiteSpace($location)) { $cmd += " --location $location" } if (-not [string]::IsNullOrWhiteSpace($group)) { $cmd += " --group $group" } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $cmd += " --provider-type $providerType" } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $cmd += " --resource $resourceName" } Invoke-MocListCommand $(" security roleassignment list $cmd") } else { Invoke-MocShowCommand $(" security roleassignment show --name ""$name""") } } function Remove-MocRoleAssignmentByName { <# .DESCRIPTION Removes a role assignment to an identity from moc using a generated role assignment name. .PARAMETER name Name of the role assignment. #> param ( [Parameter(Mandatory=$true)] [String]$name ) return Invoke-MocCommand " security roleassignment delete --name $name" } function Remove-MocRoleAssignment { <# .DESCRIPTION Removes a role assignment to an identity from moc. .PARAMETER name Name of the role assignment. #> param ( [Parameter(Mandatory=$true)] [String]$identityName, [Parameter(Mandatory=$true)] [String]$roleName, [String]$location, [String]$group, [string]$providerType, [string]$resourceName ) $cmd = "" if (-not [string]::IsNullOrWhiteSpace($location)) { $cmd += " --location $location" } if (-not [string]::IsNullOrWhiteSpace($group)) { $cmd += " --group $group" } if (-not [string]::IsNullOrWhiteSpace($providerType)) { $cmd += " --provider-type $providerType" } if (-not [string]::IsNullOrWhiteSpace($resourceName)) { $cmd += " --resource $resourceName" } return Invoke-MocCommand " security roleassignment delete --identity $identityName --role $roleName $cmd" } #endregion moc role assignment #region moc role function New-MocRole { <# .DESCRIPTION Add new role resource to moc. .PARAMETER name Name of the role. .PARAMETER actionOperations List of actions to be allowed in role. .PARAMETER actionProviders List of providers to which the corresponding actions are applied to. .PARAMETER notActionOperations List of actions to be excluded in role. .PARAMETER notActionProviders List of providers to which the corresponding excluded actions are applied to. #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string[]]$actionOperations, [Parameter(Mandatory=$true)] [string[]]$actionProviders, [Parameter(Mandatory=$false)] [string[]]$notActionOperations, [Parameter(Mandatory=$false)] [string[]]$notActionProviders ) Write-Status -moduleName $moduleName "Creating moc role" if ($actionOperations.Count -eq 0) { throw "Must pass at least one actionOperations" } if ($actionOperations.Length -ne $actionProviders.Length) { throw "Number of actionOperations not equal to number of actionProviders" } if ($notActionOperations -and $notActionOperations.Length -ne $notActionProviders.Length) { throw "Number of notActionOperations not equal to number of notActionProviders" } $yaml = @" name: $name roleproperties: permissions: - actions: "@ for ([int]$i = 0; $i -lt $actionOperations.Length; $i++) { $action = $actionOperations[$i] $provider = $actionProviders[$i] $yaml += "`n" + @" - operation: $action "@ if (-not [string]::IsNullOrWhiteSpace($name)) { $yaml += "`n" + @" provider: $provider "@ } } if ($notActionOperations -and $notActionOperations.Count -gt 0) { $yaml += "`n" + @" notactions: "@ for ([int]$i = 0; $i -lt $notActionOperations.Length; $i++) { $action = $notActionOperations[$i] $provider = $notActionProviders[$i] $yaml += "`n" + @" - operation: $action "@ if (-not [string]::IsNullOrWhiteSpace($name)) { $yaml += "`n" + @" provider: $provider "@ } } } $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " security role create --config $yamlFile" } function Get-MocRole { <# .DESCRIPTION Get a Role resource from moc. .PARAMETER name Name of the Role. #> param ( [String]$name ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security role list") } else { Invoke-MocShowCommand $(" security role show --name ""$name""") } } function Remove-MocRole { <# .DESCRIPTION Removes a role resource from moc. .PARAMETER name Name of the role. #> param ( [Parameter(Mandatory=$true)] [String]$name ) return Invoke-MocCommand " security role delete --name $name" } #endregion moc role #region moc login function Invoke-NodeLogin { <# .DESCRIPTION Provisions the Script to have access to node ctl .PARAMETER nodeName The node to execute on. #> param ( [Parameter(Mandatory=$true)] [String]$nodeName, [Parameter(Mandatory=$true)] [String]$loginYaml ) Invoke-Command -ComputerName $nodeName -ScriptBlock { $nodeloginYAMLLocation = $args[0] $nodectlPath = $args[1] Invoke-Expression "& '$nodectlPath' security login --loginpath $nodeloginYAMLLocation --identity" } -ArgumentList @($loginYaml, $global:nodeCtlFullPath) } #endregion moc login #region moc key vault function New-MocKeyVault { <# .DESCRIPTION Adds a keyvault resource to moc . .PARAMETER name Name of the keyvault .PARAMETER group The name of the group in which the keyvault resides #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group ) $yaml = @" name: "$name" "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " security keyvault create --config $yamlFile --group $group" } function Get-MocKeyVault { <# .DESCRIPTION Get a KeyVault resource from moc. .PARAMETER name Name of the KeyVault .PARAMETER group group for the KeyVault #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault list --group $group") } else { Invoke-MocShowCommand $(" security keyvault show --name ""$name"" --group $group") } } function Remove-MocKeyVault { <# .DESCRIPTION Removes a KeyVault resource from moc. .PARAMETER name Name of the KeyVault .PARAMETER group group for the KeyVault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" security keyvault delete --name ""$name"" --group "+$group) } function Reset-MocKeyVault { <# .DESCRIPTION Remove all moc keyvault. .PARAMETER group group for the NetworkInterface #> param ( [Parameter(Mandatory=$true)] [String]$group ) Invoke-MocCommand $(" security keyvault list --output tsv --query ""[*].name"" --group $group") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocKeyVault -name $entityName -group $group } } } #endregion moc key vault #region moc secret function New-MocSecret { <# .DESCRIPTION Adds a secret resource to moc . .PARAMETER name Name of the secret .PARAMETER group The name of the group in which the secret resides .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [string]$value, [Parameter(Mandatory=$true)] [string]$keyvaultName ) $yaml = @" name: "$name" value: $value secretproperties: vaultname: $keyvaultName "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " security keyvault secret set --config $yamlFile --group $group --vault-name $keyvaultName" } function Get-MocSecret { <# .DESCRIPTION Get a Secret resource from moc. .PARAMETER name Name of the Secret .PARAMETER group group for the Secret .PARAMETER keyvaultName name of the keyvault #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault secret list --group $group --vault-name $keyvaultName") } else { Invoke-MocShowCommand $(" security keyvault secret show --name ""$name"" --group $group --vault-name $keyvaultName") } } function Remove-MocSecret { <# .DESCRIPTION Removes a Secret resource from moc. .PARAMETER name Name of the Secret .PARAMETER group group for the Secret .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) Invoke-MocCommand $(" security keyvault secret delete --name ""$name"" --group $group --vault-name $keyvaultName") } function Reset-MocSecret { <# .DESCRIPTION Remove all moc secret. .PARAMETER group group for the NetworkInterface .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) Invoke-MocCommand $(" security keyvault secret list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocSecret -name $entityName -group $group -keyvaultName $keyvaultName } } } #endregion moc secret #region moc key function New-MocKey { <# .DESCRIPTION Adds a key resource to moc . .PARAMETER name Name of the key .PARAMETER group The name of the group in which the key resides .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [string]$group, [Parameter(Mandatory=$true)] [ValidateSet('RSA', 'AES')] [string]$type, [Parameter(Mandatory=$true)] [ValidateSet(256, 2048)] [string]$size, [Parameter(Mandatory=$true)] [string]$keyvaultName ) $yaml = @" name: "$name" keyproperties: vaultname: $keyvaultName keysize: $size keytype: $type "@ $yamlFile = Set-MocYaml -yamlData $yaml Invoke-MocCommand " security keyvault key create --config $yamlFile --group $group --vault-name $keyvaultName" } function Get-MocKey { <# .DESCRIPTION Get a Key resource from moc. .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) if ([string]::IsNullOrWhiteSpace($name)) { Invoke-MocListCommand $(" security keyvault key list --group $group --vault-name $keyvaultName") } else { Invoke-MocShowCommand $(" security keyvault key show --name ""$name"" --group $group --vault-name $keyvaultName") } } function Invoke-MocKeyEncrypt { <# .DESCRIPTION Encrypt input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64' ) Invoke-MocCommand $("security keyvault key encrypt --name $name --group $group --vault-name $keyvaultName --file $inputDataFile --out-file $outputDataFile --data-type $inputType") } function Invoke-MocKeyDecrypt { <# .DESCRIPTION Decrypt input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64' ) Invoke-MocCommand $("security keyvault key decrypt --name $name --group $group --vault-name $keyvaultName --file $inputDataFile --out-file $outputDataFile --data-type $inputType") } function Invoke-MocKeyWrap { <# .DESCRIPTION Wrap input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64' ) Invoke-MocCommand $("security keyvault key wrap --name $name --group $group --vault-name $keyvaultName --file $inputDataFile --out-file $outputDataFile --data-type $inputType") } function Invoke-MocKeyUnwrap { <# .DESCRIPTION Unwrap input data using key .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$inputDataFile, [Parameter(Mandatory=$true)] [string]$outputDataFile, [Parameter(Mandatory=$false)] [ValidateSet('plaintext','base64')] [string]$inputType = 'base64' ) Invoke-MocCommand $("security keyvault key unwrap --name $name --group $group --vault-name $keyvaultName --file $inputDataFile --out-file $outputDataFile --data-type $inputType") } function Get-MocKeyPublicKey { <# .DESCRIPTION Downloads Public Key if keytype is RSA .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName, [Parameter(Mandatory=$true)] [string]$outputFile ) Invoke-MocCommand $("security keyvault key download --name $name --group $group --vault-name $keyvaultName --file-path $outputFile") } function Remove-MocKey { <# .DESCRIPTION Get a Key resource from moc. .PARAMETER name Name of the Key .PARAMETER group group for the Key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$name, [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) Invoke-MocCommand $(" security keyvault key delete --name ""$name"" --group $group --vault-name $keyvaultName") } function Reset-MocKey { <# .DESCRIPTION Remove all moc key .PARAMETER group group for the key .PARAMETER keyvaultName name of the keyvault #> param ( [Parameter(Mandatory=$true)] [String]$group, [Parameter(Mandatory=$true)] [string]$keyvaultName ) Invoke-MocCommand $(" security keyvault key list --output tsv --query ""[*].name"" --group $group --vault-name $keyvaultName") | ForEach-Object { $entityName = $_ if ($entityName -notlike "No *") { Remove-MocKey -name $entityName -group $group -keyvaultName $keyvaultName } } } #endregion moc key #region moc backup function Invoke-MocBackup { <# .DESCRIPTION Removes a group resource from moc. .PARAMETER path Path to backup to #> param ( [Parameter(Mandatory=$true)] [String]$path ) Invoke-MocCommand $(" admin recovery backup --path $path") } #endregion moc backup #end region function Get-NodeAgentVersion { <# .DESCRIPTION Executes a nodeagent command. #> $cmdArgs = " version" $tmp = Invoke-CommandLine -Command $global:nodeAgentBinary -Arguments $cmdArgs -moduleName $moduleName return $tmp[2] } function Get-MocAgentVersion { <# .DESCRIPTION Executes a nodeagent command. #> $cmdArgs = " version" $tmp = Invoke-CommandLine -Command $global:cloudAgentBinary -Arguments $cmdArgs -moduleName $moduleName return $tmp[2] } function Set-DeploymentType { <# .DESCRIPTION Determine what type of deployment is being performed (single node or multi-node). #> Write-Status -moduleName $moduleName "Determining deployment type" # Important to save the cluster information globally for subsequent function calls to use $failoverCluster = Get-FailoverCluster if ($failoverCluster) { Write-SubStatus -moduleName $moduleName $("This is a multi-node deployment using failover cluster: " + $failoverCluster.Name) # Sanity check. if ((Test-LocalFilePath -path $global:config[$moduleName]["imageDir"]) -or (Test-LocalFilePath -path $global:config[$moduleName]["cloudConfigLocation"])) { throw $("For multi-node deployments you must set both -imageDir and -cloudConfigLocation parameters to a network share or CSV path. Please use Set-MocConfig to correct this issue.") } # Set additional cluster related globals for later use Set-MocConfigValue -name "cloudFqdn" -value ($global:config[$moduleName]["clusterRoleName"] + "." + $failoverCluster.Domain) Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::MultiNode) } else { Write-SubStatus -moduleName $moduleName $("This is a single-node deployment") $hostname = ([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname # If this single node hostname is a short name, we use the host ip to avoid DNS resolution issues if ($hostname -notlike '*.*') { $hostname = Get-HostIp -nodeName localhost } Set-MocConfigValue -name "cloudFqdn" -value $hostname Set-MocConfigValue -name "deploymentType" -value ([DeploymentType]::SingleNode) } } function Test-Remoting { <# .DESCRIPTION Validate that powershell remoting to a node is working. .PARAMETER nodeName The node to remote to. #> param ( [String]$nodeName ) try { Invoke-Command -ComputerName $nodeName -ScriptBlock { return $null } -ErrorAction Stop return $true } catch {} return $false } function Test-CloudLimits { <# .DESCRIPTION Verify the resource limits for all nodes in the cloud. .PARAMETER path Path to WSSD Image directory. .PARAMETER minRequiredDisk Minimimum required disk space (GB). .PARAMETER minRequiredMemory Minimimum required memory (GB). .PARAMETER minRequiredLp Minimimum required logical processor. #> param ( [string]$path, [int]$minRequiredDisk, [int]$minRequiredMemory, [int]$minRequiredLp ) Write-Status "Verifying Cloud limits" -moduleName $moduleName if (Test-MultiNodeDeployment) { if($path.ToLower().IndexOf("$env:SystemDrive\clusterstorage".ToLower()) -eq 0) { Write-SubStatus "Checking available space in shared drive" -moduleName $moduleName $space = Get-ClusterSharedVolume -ErrorAction Stop | Where-Object {$path.ToLower().IndexOf($_.SharedVolumeInfo.FriendlyVolumeName.ToLower()) -eq 0} if ($null -ne $space) { $freespaceGb = [Math]::Round($space.SharedVolumeInfo.Partition.FreeSpace / 1GB) } else { throw $("Missing drive name in shared storage '$path'.") } Write-SubStatus "Shared Drive has $freespaceGb GB free" -moduleName $moduleName Write-SubStatus "A minimum of $minRequiredDisk GB disk space is required on shared drive" -moduleName $moduleName if ($freespaceGb -lt $minRequiredDisk) { throw $("Not enough disk space is available on shared drive '$drive'.") } } Get-ClusterNode -ErrorAction Stop | ForEach-Object { Test-HostLimits -nodeName $_.Name -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp } } else { $drive = Split-Path -Path $path -Qualifier $disk = Get-WmiObject Win32_LogicalDisk -Filter $("DeviceID='$drive'") | Select-Object Size,FreeSpace $freespaceGb = [Math]::Round($Disk.Freespace / 1GB) Write-SubStatus "Drive '$drive' has $freespaceGb GB free" -moduleName $moduleName Write-SubStatus "A minimum of $minRequiredDisk GB disk space is required on drive '$drive'" -moduleName $moduleName if ($freespaceGb -lt $minRequiredDisk) { throw $("Not enough disk space is available on drive '$drive'.") } Test-HostLimits -nodeName ($env:computername) -minRequiredMemory $minRequiredMemory -minRequiredLp $minRequiredLp } } function Test-HostLimits { <# .DESCRIPTION Verify the resource limits for a host node. .PARAMETER nodeName The node to execute on. .PARAMETER minRequiredMemory Minimimum required memory (GB). .PARAMETER minRequiredLp Minimimum required LogicalProcessor. #> param ( [string]$nodeName, [int]$minRequiredMemory, [int]$minRequiredLp ) Write-Status "Verifying host limits on $nodeName" -moduleName $moduleName Invoke-Command -ComputerName $nodeName -ScriptBlock { $minRequiredMemory = $args[0] $minRequiredLp = $args[1] $freemem = Get-WmiObject -Class Win32_OperatingSystem $freemem = [Math]::Round($freemem.FreePhysicalMemory / 1MB) write-verbose $("Host has $freemem GB free memory") write-verbose $("A minimum of $minRequiredMemory GB memory is required") if ($freemem -lt $minRequiredMemory) { throw $("Not enough memory is available.") } $lpCount = (Get-ComputerInfo -Property CsNumberOfLogicalProcessors).CsNumberOfLogicalProcessors write-verbose $("Host has $lpCount logical processors") write-verbose $("A minimum of $minRequiredLp logical processors is required") if ($lpCount -lt $minRequiredLp) { throw $("Not enough logical processors available.") } } -ArgumentList $minRequiredMemory, $minRequiredLp } function Confirm-Remoting { <# .DESCRIPTION Confirm that powershell remoting is enabled and working for the cloud. .PARAMETER localhostOnly Limit the validation to only the local node. #> param ( [Switch]$localhostOnly ) if ((-not $localhostOnly.IsPresent) -and (Test-MultiNodeDeployment)) { # Multi-node Get-ClusterNode -ErrorAction Stop | ForEach-Object { $hostname = $_.Name Write-Status "Testing powershell remoting to $hostname..." -moduleName $moduleName if (-not (Test-Remoting -nodeName $hostname)) { throw "Powershell remoting doesn't seem to be enabled on '$hostname'. Please ensure that you have run the initialize cmdlet on every node!" } Write-SubStatus $("Remoting to $hostname was successful") -moduleName $moduleName } } else { # Single node or localhostOnly Write-Status "Testing powershell remoting to $env:computername..." -moduleName $moduleName if (-not (Test-Remoting -nodeName $env:computername)) { Write-SubStatus $("Remoting does not seem to be enabled. Enabling it now...") -moduleName $moduleName Enable-Remoting if (-not (Test-Remoting -nodeName $env:computername)) { throw "Powershell remoting to "+$env:computername+" was not successful." } } Write-SubStatus $("Remoting to "+$env:computername+" was successful") -moduleName $moduleName } } function Confirm-Configuration { <# .DESCRIPTION Validate if the configuration can be used for the deployment #> param ( [String] $workingDir, [String] $cloudConfigLocation, [Switch] $skipHostLimitChecks, [Switch] $skipRemotingChecks, [Switch] $useStagingShare, [String] $stagingShare ) if (!$skipRemotingChecks.IsPresent) { Confirm-Remoting } if ([string]::IsNullOrWhiteSpace($workingDir)) { throw "Empty workingDir" } if ([string]::IsNullOrWhiteSpace($cloudConfigLocation)) { throw "Empty cloudConfigLocation" } #Check HCI node registration status $multiNode = Test-MultiNodeDeployment if ($multiNode) { Test-ClusterHealth Test-HCIRegistration } if (!$skipHostLimitChecks.IsPresent) { $tmpDir = $workingDir # Skip SMB path if (!$tmpDir.StartsWith("\\")) { if ($multiNode) { Test-CloudLimits -path $tmpDir -minRequiredDisk 40 -minRequiredMemory 10 -minRequiredLp 4 } else { Test-CloudLimits -path $tmpDir -minRequiredDisk 50 -minRequiredMemory 20 -minRequiredLp 4 } } } if ($useStagingShare.IsPresent -and [string]::IsNullOrWhiteSpace($stagingShare)) { throw "-useStagingShare was requested, but no staging share was specified" } } function Get-GuestVirtualMachineLogs { <# .DESCRIPTION Collects logs from Virtual Machines via scp .PARAMETER logDirectoryName Output directory for the logs .PARAMETER activity Activity name to use when writing progress #> param ( [Parameter()] [String] $logDirectoryName, [Parameter()] [String] $activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Discovering cloud groups..." $groups = Invoke-MocCommand ("cloud group list --output tsv --query ""[*].name"" --location " + $global:config[$modulename]["cloudLocation"]) foreach ($grpName in $groups) { if ($grpName -ieq "No Group Resources") { continue } # Make Resource Group directory $logGroupDir = [io.Path]::Combine($logDirectoryName, $grpName) New-Item -ItemType Directory -Force -Path $logGroupDir | Out-Null try { $virtualMachines = Invoke-MocCommand "compute vm list -o tsv --query ""[*].name"" --group ""$grpName""" foreach ($vmName in $virtualMachines) { if ($vmName -ieq "No VirtualMachine Resources") { continue } Write-StatusWithProgress -activity $activity -moduleName $moduleName -status "Gather logs for virtual machine '$vmName'..." try { $nicName = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.networkprofile.networkinterfaces[0] --group ""$grpName""" $cleanNicName = $nicName.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join '' $ipAddress = Invoke-MocCommand "network vnic show --name ""$cleanNicName"" -o tsv --query properties.ipConfigurations[0].properties.privateIPAddress --group ""$grpName""" if ([string]::IsNullOrWhiteSpace($ipAddress) -or $ipAddress -ieq "no virtual network interface resources") { throw $("Collecting ""$vmName"" logs was not successful") } # Remove cached keys belonging to this ip from the known_hosts file. # This will avoid scp/ssh warnings being shown in redeployment scenarios where stale data may exist for a host/ip in the known_hosts file. (& ssh-keygen -R ${ipAddress}) 2>&1>$null # Make VM directory $logVMDir = [io.Path]::Combine($logDirectoryName, $grpName) New-Item -ItemType Directory -Force -Path $logVMDir | Out-Null Write-SubStatus -moduleName $moduleName "Grabbing CloudInit logs from: ""$vmName""" $osType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o yaml --query virtualmachineproperties.osprofile.osType --group ""$grpName""" if ($osType -like 'Windows') { $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_logs.zip") $randomFolder=$(New-Guid) (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "Administrator@${ipAddress}" "mkdir C:\tmp\$randomFolder && pushd C:\tmp\$randomFolder && powershell.exe C:\Packages\collect-windows-logs.ps1") 2>&1>$null if ($?) { (& scp -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "Administrator@${ipAddress}:/tmp/$randomFolder/*.zip" $logDirFile) 2>$null (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "Administrator@${ipAddress}" "powershell.exe Remove-Item -Recurse -Force C:\tmp\$randomFolder") 2>$null } else { Write-SubStatus -moduleName $moduleName "Failed to get logs from: ""$vmName""" } # Windows VM log collection is over continue } $logDirFile = [io.Path]::Combine($logVMDir, "${vmName}_cloudinit.log") (& scp -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}:/var/log/cloud-init-output.log" $logDirFile) 2>$null Write-SubStatus -moduleName $moduleName "Grabbing systemd logs from: ""$vmName""" $systemdLogFile = [io.Path]::Combine($logVMDir, "${vmName}_systemd.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl --output=short-precise -n $global:defaultLogLineCount) > $systemdLogFile # Check if LB VM Type $vmType = Invoke-MocCommand "compute vm show --name ""$vmName"" -o tsv --query virtualmachineproperties.vmType --group ""$grpName""" $cleanVmType = $vmType.Split('', [System.StringSplitOptions]::RemoveEmptyEntries) -join '' if ($cleanVmType -ieq "LoadBalancer") { if ($ipAddress -ine "no virtual network interface resources") { Write-SubStatus -moduleName $moduleName "Grabbing HAProxy configuration from: ""$vmName""" $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.cfg") (& scp -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}:/etc/haproxy/haproxy.cfg" $lbLogDirFile) 2>$null Write-SubStatus -moduleName $moduleName "Grabbing Keepalived configuration from: ""$vmName""" $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.conf") (& scp -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}:/etc/keepalived/keepalived.conf" $lbLogDirFile) 2>$null Write-SubStatus -moduleName $moduleName "Grabbing HAProxy logs from: ""$vmName""" $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_haproxy.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl -u haproxy -n $global:defaultLogLineCount) > $lbLogDirFile Write-SubStatus -moduleName $moduleName "Grabbing Keepalived logs from: ""$vmName""" $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_keepalived.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl -u keepalived -n $global:defaultLogLineCount) > $lbLogDirFile Write-SubStatus -moduleName $moduleName "Grabbing lbagent logs from: ""$vmName""" $lbLogDirFile = [io.Path]::Combine($logVMDir, "${vmName}_lbagent.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl -u lbagent -n $global:defaultLogLineCount) > $lbLogDirFile } } else { Write-SubStatus -moduleName $moduleName "Grabbing docker logs from: ""$vmName""" $dockerLogFile = [io.Path]::Combine($logVMDir, "${vmName}_docker.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl --no-pager -u docker -n $global:defaultLogLineCount) > $dockerLogFile Write-SubStatus -moduleName $moduleName "Grabbing kubelet logs from: ""$vmName""" $kubeletLogFile = [io.Path]::Combine($logVMDir, "${vmName}_kubelet.log") (& ssh -i $global:config[$modulename]["sshPrivateKey"] -o StrictHostKeyChecking=no "clouduser@${ipAddress}" sudo journalctl --no-pager -u kubelet -n $global:defaultLogLineCount) > $kubeletLogFile } } catch [Exception] { Write-Status -msg "Exception caught!!!" -moduleName $moduleName Write-SubStatus -msg $_.Exception.Message.ToString() -moduleName $moduleName Write-SubStatus -msg "Could not get CloudInit Logs for ""$vmName""" -moduleName $moduleName } } } catch [Exception] { Write-Status -msg "Exception caught!!!" -moduleName $moduleName Write-SubStatus -msg $_.Exception.Message.ToString() -moduleName $moduleName } } } function Wait-ForActiveNodes { <# .DESCRIPTION Waits for all nodes in the cloud to be in Active state. .PARAMETER sleepDuration Duration to sleep for between attempts .PARAMETER timeout Duration until timeout of waiting .PARAMETER location Location for the nodes. .PARAMETER activity Activity name to use when updating progress #> param ( [int]$sleepDuration=20, [int]$timeout=1800, #seconds in 30min [string]$location = $global:defaultCloudLocation, [String]$activity = $MyInvocation.MyCommand.Name ) Write-StatusWithProgress -activity $activity -moduleName $moduleName -status $("Waiting for cloud nodes to be active...") $nodeCount = 1 if (Test-MultiNodeDeployment) { $nodeCount = (Get-ClusterNode -ErrorAction SilentlyContinue).Count } ## Start the timer $timer = [Diagnostics.Stopwatch]::StartNew() while(($timer.Elapsed.TotalSeconds -lt $timeout)) { $activeNodes = $null try { $activeNodes = Invoke-MocCommand "cloud node list -o json --query ""[?properties.statuses.RunningState=='Active']"" --location $location" | ConvertFrom-Json } catch { # When no nodes are Active, ConvertFrom-Json will throw an exception if (-not ($_.Exception.Message -like "*Invalid JSON primitive: No.*")) { Write-SubStatus -moduleName $moduleName $("Warning: " + $_.Exception.Message) } } if ($null -ne $activeNodes) { $numActive = ($activeNodes).Count if ($nodeCount -eq $numActive) { Write-SubStatus -moduleName $moduleName $("All nodes are Active.") return $true } } Start-Sleep $sleepDuration } return $false } #endregion # SIG # Begin signature block # MIIjgwYJKoZIhvcNAQcCoIIjdDCCI3ACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB6WmwQScnsXz6A # pwRuSIBLtJvceYcdh4gzXeFDMZz5MaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw # LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn # s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw # PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS # yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG # 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh # EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH # tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS # 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp # TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok # t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 # b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao # mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD # Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt # VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G # CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ # Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 # oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVWDCCFVQCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgPq4GOIBK # t2a78jPxCyeWdebd7D7Imv4qw3Y8eI32tCQwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQAcf5NOGh16sTELM76vxf3damw7ceiJGK1jBYZyUhjI # 4eFPfRGPOTxH2tYNdB5NfkZ0m0RWL0lNbkUhMXYQiTDtt48LftNOw6UvMCulojLD # A/xGeBOQC05Jt/ybym0NzzJGGScDyTVtYAzqmSKixBXqC7MMi4In6drZfD9k1u2m # 9uJhLGqnTswPI5VN//0w1s0mK28qWDrIQENzv9jR+Q13ilzgxyJiz37DoNvNDf2I # hF6J2Phe0P2zJuNuZl1rb9pnMR8ZZ/S+JxWsUPHMLeXrkuQAAN0H4TFDbeTsSF2x # SjtWAGaVcOKTDi/HHYUjft+0s3+x3Zq3bunWWVIGASeAoYIS4jCCEt4GCisGAQQB # gjcDAwExghLOMIISygYJKoZIhvcNAQcCoIISuzCCErcCAQMxDzANBglghkgBZQME # AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIBnc7o1T+cX7xy21dz8UgWpjz2YNbELja5N8W93g # lDV/AgZgrld7TwcYEzIwMjEwNjA1MDAxNDA3Ljg5OVowBIACAfSggdCkgc0wgcox # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p # Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOkU1QTYtRTI3Qy01OTJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNloIIOOTCCBPEwggPZoAMCAQICEzMAAAFHnY/x5t4xg1kAAAAAAUcw # DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN # MjAxMTEyMTgyNTU1WhcNMjIwMjExMTgyNTU1WjCByjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RTVBNi1FMjdDLTU5 # MkUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtBQNM6X32KFk/BJ8YaprfzEt6Lj34 # G+VLjzgfEgOGSVd1Mu7nCphK0K4oyPrzItgNRjB4gUiKq6GzgxdDHgZPgTEvm57z # sascyGrybWkf3VVr8bqf2PIgGvwKDNEgVcygsEbuWwXz9Li6M7AOoD4TB8fl4ATm # +L7b4+lYDUMJYMLzpiJzM745a0XHiriUaOpYWfkwO9Hz6uf+k2Hq7yGyguH8naPL # MnYfmYIt2PXAwWVvG4MD4YbjXBVZ14ueh7YlqZTMua3n9kT1CZDsHvz+o58nsoam # XRwRFOb7LDjVV++cZIZLO29usiI0H79tb3fSvh9tU7QC7CirNCBYagNJAgMBAAGj # ggEbMIIBFzAdBgNVHQ4EFgQUtPjcb95koYZXGy9DPxN49dSCsLowHwYDVR0jBBgw # FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx # MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN # BgkqhkiG9w0BAQsFAAOCAQEAUMQOyjV+ea2kEtXqD0cOfD2Z2PFUIy5kLkGU53RD # GcfhlzIR9QlTgZLqTEhgLLuCSy6jcma+nPg7e5Xg1oqCZcZJRwtRPzS1F6/M6YR3 # 5H3brN0maVnPrmrQ91kkfsNqDTtuWDiAIBfkNEgCpQZCb4OV3HMu5L8eZzg5dUaJ # 7XE+LBuphJSLFJtabxYt4fkCQxnTD2z50Y32ZuXiNmFFia7qVq+3Yc3mmW02+/KW # H8P1HPiobJG8crGYgSEkxtkUXGdoutwGWW88KR9RRcM/4GKLqt2OQ8AWEQb7shgM # 8pxNvu30TxejRApa4WAfOAejTG4+KzBm67XjVZ2IlXAPkjCCBnEwggRZoAMCAQIC # CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp # ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx # NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF # ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD # DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx # z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1 # rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc # sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB # 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF # bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud # EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD # VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv # cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB # BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j # ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB # kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe # MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA # LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx # vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS # inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1 # L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO # M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4 # pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45 # V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x # 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe # gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn # QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp # 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT # X4/edIhJEqGCAsswggI0AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEG # A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj # cm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBP # cGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpFNUE2LUUyN0MtNTky # RTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcG # BSsOAwIaAxUAq6fBtEENocNASMqL03zGJS0wZd2ggYMwgYCkfjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAORksygwIhgPMjAy # MTA2MDQyMjExNTJaGA8yMDIxMDYwNTIyMTE1MlowdDA6BgorBgEEAYRZCgQBMSww # KjAKAgUA5GSzKAIBADAHAgEAAgIcTTAHAgEAAgISFjAKAgUA5GYEqAIBADA2Bgor # BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAID # AYagMA0GCSqGSIb3DQEBBQUAA4GBAMOoSfROadGJDK1AmIxyhHuQBIxixFpedu75 # er71OZMUbR4UC1VT0eIRMsHPOI0Kn7CWF+phNmUCnIivzqG77u2GSMuHwkkZMEIh # V8rJTUWsudZYbJ6geV7K6ftN9r3pyqGFidRQL0jJvbDrxCl8oVzhjZVPrSw22Bni # 6CTrcUwzMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAFHnY/x5t4xg1kAAAAAAUcwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG # SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgM9T+9oG/uagn # T/n3c/r8Rt5BfJj6zM1/8Dti8oaUmOgwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk # MIG9BCB72zwSA5TPugbIiZO/2H1hrisAVItwzDscb0WqihjphTCBmDCBgKR+MHwx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p # Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABR52P8ebeMYNZAAAAAAFH # MCIEIKBAYXI3rGHa8i6J3hCeH7z46Wc8wFm35dyvW4uV7HCWMA0GCSqGSIb3DQEB # CwUABIIBAB8sf7S17UfyS07LaaotYw2qyRoRBuo0ttI4cSXwI6xRYfDJR5cZ5AFF # mQ5dNUExta8CuCOMFjLripj8jfeaWrXM0kHQATbm2X0kTL/ynKmxafenMobTYRSL # ZB9ofYsStuV23NRDUhMcL1MmEArSl7sytLRjdtpMCojl1NOHuJAtodmLVRSx/50r # mMAdDYxuJ8xxmHy51IJKoQVpxe4BQkSLMGd1uvTPQLiyy6SGde1a09Zy9uhAX45C # +ifmUbmkEN3Ogdu3Tap/VI3ponh3sG5wqrlk4Ragf7HRHFsCHmfzFMQG4xoCKnFw # 9aI+9U4+PlJ6UmICy7qEsnBy2eu4Qog= # SIG # End signature block |