roeprofile.psm1
Function Clear-UserVariables { #Clear any variable not defined in the $SysVars variable $UserVars = get-childitem variable: | Where {$SysVars -notcontains $_.Name} ForEach ($var in $UserVars) { Write-Host ("Clearing $" + $var.name) Remove-Variable $var.name -Scope 'Global' } } Function Connect-EXOPartner { param( [parameter(Mandatory = $false)] [System.Management.Automation.CredentialAttribute()] $Credential, [parameter(Mandatory = $false)] [string] $TenantDomain ) if (-not $TenantDomain) { $TenantDomain = Read-Host -Prompt "Input tenant domain, e.g. hosters.com" } if (-not $Credential) { $Credential = Get-Credential -Message "Credentials for CSP delegated admin, e.g. ""bm@klestrup.dk""/""password""" } $ExSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$TenantDomain" -Credential $Credential -Authentication Basic -AllowRedirection if ($ExSession) {Import-PSSession $ExSession} } Function ConvertTo-HashTable { # https://stackoverflow.com/questions/3740128/pscustomobject-to-hashtable param ( [Parameter(ValueFromPipeline)] $PSObject ) process { if ($null -eq $PSObject -and $null -eq $JSON ) { return $null } if ($PSObject -is [string]) { $PSObject = $PSObject | ConvertFrom-Json } if ($PSObject -is [System.Collections.IEnumerable] -and $PSObject -isnot [string]) { $collection = @( foreach ($object in $PSObject) { ConvertTo-HashTable $object } ) Write-Output -NoEnumerate $collection } elseif ($PSObject -is [psobject]) { $hash = [ordered]@{} foreach ($property in $PSObject.PSObject.Properties) { $hash[$property.Name] = ConvertTo-HashTable $property.Value } return $hash } else { return $PSObject } } } Function Get-COMObjects { if (-not $IsLinux) { $Objects = Get-ChildItem HKLM:\Software\Classes -ErrorAction SilentlyContinue | Where-Object {$_.PSChildName -match '^\w+\.\w+$' -and (Test-Path -Path "$($_.PSPath)\CLSID")} $Objects | Select-Object -ExpandProperty PSChildName } } Function Get-NetIPAdapters { Param( [Parameter(Mandatory = $false)] [String[]]$ComputerName ) if ($ComputerName.length -lt 1 -or $computername.Count -lt 1) { $computername = @($env:COMPUTERNAME) } $OutPut = @() #Vis netkort med tilhørende IP adr. foreach ($pc in $Computername) { $OutPut += Get-NetAdapter -CimSession $pc | Select-Object Name,InterfaceDescription,IfIndex,Status,MacAddress,LinkSpeed,@{N="IPv4";E={(Get-NetIpaddress -CimSession $pc -InterfaceIndex $_.ifindex -AddressFamily IPv4 ).IPAddress}},@{N="IPv6";E={(Get-NetIpaddress -CimSession $pc -InterfaceIndex $_.ifindex -AddressFamily IPv6 ).IPAddress}},@{N="Computer";E={$pc}} | Sort-Object -Property Name } $OutPut } Function Get-StringASCIIValues { [CMDLetbinding()] param ( [string]$String ) return $String.ToCharArray() | ForEach-Object {$_ + " : " + [int][Char]$_} } Function Get-StringHash { #https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.1 (ex. 4) [CMDLetBinding()] param ( [Parameter(Mandatory = $true)] [string[]]$String, [Parameter(Mandatory = $false)] [ValidateSet("SHA1","SHA256","SHA384","SHA512","MACTripleDES","MD5","RIPEMD160")] [string]$Algorithm = "SHA256", [Parameter(Mandatory = $false)] [int]$GroupCount = 2, [Parameter(Mandatory = $false)] [String]$Seperator = "-" ) $Result = @() Write-Verbose "Received $($String.count) string(s)" Write-Verbose "Using algorithm: $Algorithm" $stringAsStream = [System.IO.MemoryStream]::new() $writer = [System.IO.StreamWriter]::new($stringAsStream) foreach ($t in $String) { $writer.write($t) $writer.Flush() $stringAsStream.Position = 0 $HashString = Get-FileHash -InputStream $stringAsStream -Algorithm $Algorithm | Select-Object -ExpandProperty Hash Write-Verbose "$($HashString.length) characters in hash" } Write-Verbose "Dividing string to groups of $GroupCount characters, seperated by $Seperator" for ($x = 0 ; $x -lt $HashString.Length ; $x = $x + $GroupCount) { $Result += $HashString[$x..($x + ($GroupCount -1))] -join "" } Write-Verbose "$($Result.count) groups" $Result = $Result -join $Seperator Write-Verbose "Returning $($Result.length) character string" return $Result } Function Get-UNCPath { param( [parameter(Mandatory=$true)] [String]$Path ) if (-not $IsLinux) { $DriveLetter = ($Path)[0] if ($DriveLetter -match "[a-z]") { $PSDrive = Get-PSDrive | Where-Object {$_.Name -eq $DriveLetter} if ($PSDrive.DisplayRoot) { $Path = $Path.Substring(3,($Path.length -3)) $Path = $PSdrive.DisplayRoot + "\" + $Path } } } return $Path } Function Get-UserVariables { #Get, and display, any variable not defined in the $SysVars variable get-childitem variable: | Where-Object {$SysVars -notcontains $_.Name} } Function Get-WebRequestError { <# .SYNOPSIS Read more detailed error from failed Invoke-Webrequest and Invoke-RestMethod https://stackoverflow.com/questions/35986647/how-do-i-get-the-body-of-a-web-request-that-returned-400-bad-request-from-invoke .Parameter ErrorObject $_ from a Catch block #> [CMDLetbinding()] param ( [object]$ErrorObject ) $streamReader = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()) $ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json $streamReader.Close() return $ErrResp } Function New-YAMLTemplate { <# .SYNOPSIS Generates Azure Pipelines based on the comment based help in the input script .Description Generates Azure Pipelines based on the comment based help in the input script. For help on comment based help see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help?view=powershell-7.2 Script parameters are parsed, and YAML template, pipeline and, optional, variable files are generated with default values, and validateset values prepopulated. Default variables that will be defined in pipeline yaml: deployment: DeploymentDisplayName converted to lower-case, spaces replaced with underscores, and non-alphanumeric characters removed. deploymentDisplayName: Value of DeploymentDisplayName parameter For each supplied environment, default variables will be created: <ENV>_ServiceConnection: '<ENV>_<ServiceConnectionSuffix parameter value>' If no value is supplied, default value is simply "ServiceConnection" <ENV>_EnvironmentName: '<ENV>' Outputfiles will be placed in the same directory as the source script. The template for the script will have the extension .yml and the sample files for pipelines will have the extension .yml.sample .Parameter ScriptPath Path to script to generate YAML templates for .Parameter DeploymentDisplayName Display Name of deployment when pipeline is run. .Parameter Environment Name of environment(s) to deploy to .Parameter Overwrite Overwrite existing YAML files .Parameter PipelineVariablesToFile Determines if variables are written to separate YAML file(s). If omitted all variables are declared in the pipeline YAML. .Parameter ServiceConnectionSuffix Suffix to apply to serviceconnection variable values. .Parameter PowerShell7 Use PowerShell 7 .Example PS> $ScriptPath = "C:\Scripts\AwesomeScript.ps1" PS> New-YAMLTemplate -ScriptPath $ScriptPath -Environment "DEV","TEST" -Overwrite This will generate a template file and a pipeline file for deployment of C:\Scripts\AwesomeScript.ps1 to DEV and TEST environments. Existing files will be overwritten. All variables will be declared in the pipeline YAML. .Example PS> $ScriptPath = "C:\Scripts\AwesomeScript.ps1" PS> New-YAMLTemplate -ScriptPath $ScriptPath -Environment "DEV","TEST" -PipelineVariablesToFile This will generate a template file, a pipeline file and two variable files for deployment of C:\Scripts\AwesomeScript.ps1 to DEV and TEST environments. If files already exist the script will throw an error. Variables will be declared in separate files for each environment. #> [CMDLetbinding()] param ( [Parameter(Mandatory = $true)] [ValidateScript({Get-ChildItem -File -Path $_})] [String]$ScriptPath, [Parameter(Mandatory = $false)] [String]$DeploymentDisplayName = "Deployment of $(Split-Path -Path $ScriptPath -Leaf)", [Parameter(Mandatory = $false)] [String[]]$Environment = "Dev", [Parameter(Mandatory = $false)] [Switch]$Overwrite, [Parameter(Mandatory = $false)] [Switch]$PipelineVariablesToFile, [Parameter(Mandatory = $false)] [String]$ServiceConnectionSuffix = "ServiceConnection", [Parameter(Mandatory = $False)] [switch]$pwsh ) # Pipeline PowerShell task: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/powershell?view=azure-devops # Workaround to make sure we get the correct case from the filename, and not just whatever is passed to us as parameter value $ScriptName = Split-Path -Path $ScriptPath -Leaf $ScriptDirectory = Split-Path -Path $ScriptPath -Parent $ScriptFile = Get-Item -Path "$ScriptDirectory\*.*" | Where-Object {$_.Name -eq $ScriptName} # Declare variables $ScriptHelp = Get-Help -Name $ScriptPath $ScriptCommand = (Get-Command -Name $ScriptPath) $ScriptCommandParameters = $ScriptCommand.Parameters $ScriptHelpParameters = $ScriptHelp.parameters $ScriptBaseName = $ScriptFile.BaseName $VariablePrefix = $ScriptBaseName.replace("-","_").ToLower() # File names in lower case in accordance with https://dev.azure.com/itera-dk/Mastercard.PaymentsOnboarding/_wiki/wikis/Mastercard.PaymentsOnboarding.wiki/435/Repository-structure-(suggestion) $TemplateFileName = $ScriptFile.DirectoryName + "\" + $ScriptFile.Name.Replace(".ps1",".yml").ToLower() $PipelineFileName = (Split-Path -Path $TemplateFileName -Parent) + "\deploy-" + (Split-Path -Path $TemplateFileName -Leaf) + ".sample" $PipelineVariablesFileNameTemplate = $PipelineFileName.Replace(".yml.sample","-#ENV#-vars.yml.sample").ToLower() #Template for variable files. #ENV# is replace with corresponding environment name. # Optional header for the variables and pipeline files. $PipelineVariablesHeader = @() $PipelineVariablesHeader += "# If using double quotes, remember to escape special characters" $PipelineVariablesHeader += "# Booleans, and Numbers, must be passed in {{ variables.<variablename>}} to template to retain data type when received by template." $PipelineVariablesHeader += "# Booleans still need to be prefixed with $ when passed to script, because Pipelines sucks (https://www.codewrecks.com/post/azdo/pipeline/powershell-boolean/)" $PipelineVariablesHeader += "# Split long strings to multiple lines by using >- , indenting value-lines ONE level and NO single-quotes surrounding entire value (https://yaml-multiline.info/ - (folded + strip)) " # Get repo-relative paths of script if possible. Push-Location -Path (Split-Path -Path $ScriptPath -Parent) -StackName RelativePath try { $ScriptRelativePath = & git ls-files --full-name $ScriptPath if ([string]::IsNullOrWhiteSpace($ScriptRelativePath)) { $ScriptRelativePath = & git ls-files --full-name $ScriptPath --others } } catch { Write-Warning "Unable to run git commands" Write-Warning $_.Exception.Message } Pop-Location -StackName RelativePath if ([string]::IsNullOrWhiteSpace($ScriptRelativePath)) { $ScriptRelativePath = (Resolve-Path -Path $ScriptPath -Relative).replace("\","/") Write-Warning "Couldn't find path relative to repository. Is file in a repo? Are there differences in casing? Relative references will fall back to $ScriptRelativePath" } # Get relative paths for use for references in pipeline yaml $TemplateRelativePath = $ScriptRelativePath.replace(".ps1",".yml") $RelativePath = (Split-Path -Path $ScriptRelativePath -Parent).Replace("\","/") if ($PipelineVariablesToFile) { $RelativePipelineVariablesFileNameTemplate = (Split-Path -Path $PipelineVariablesFileNameTemplate -Leaf) $PipelineVariablesFileNames = @{} $RelativePipelineVariablesFileNames = @{} foreach ($e in $Environment) { $RelativePipelineVariablesFileNames.add($e,($RelativePipelineVariablesFileNameTemplate -replace("#ENV#",$e)).tolower()) # Used as reference in pipeline $PipelineVariablesFileNames.add($e,($PipelineVariablesFileNameTemplate -replace("#ENV#",$e)).ToLower()) # Used to write the files } } # Parse the parameters and get necessary values for YAML generation $ScriptParameters = @() foreach ($param in $ScriptHelpParameters.parameter) { $Command = $ScriptCommandParameters[$param.name] $Props = [ordered]@{"Description" = $param.description "Name" = $param.name "HelpMessage" = ($Command.Attributes | Where-Object {$_.GetType().Name -eq "ParameterAttribute"}).HelpMessage "Type" = $param.type "Required" = $param.required "DefaultValue" = $param.defaultValue "ValidateSet" = ($Command.Attributes | Where-Object {$_.GetType().Name -eq "ValidateSetAttribute"}).ValidValues "ValidateScript" = ($Command.Attributes | Where-Object {$_.GetType().Name -eq "ValidateScriptAttribute"}).scriptblock } # Build a description text to add to variables, and parameters, in YAML files $YAMLHelp = "" if ($props.Description.length -gt 0) { $YAMLHelp += "Description: $((($props.Description | foreach-object {$_.Text}) -join " ") -replace ("`r`n|`n|`r", " "))" } $YAMLHelp += " Required: $($param.required)" if ($Props.HelpMessage.Length -gt 0) { $YAMLHelp += " Help: $($Props.HelpMessage)" } if ($Props.ValidateSet.Count -gt 0) { $YAMLHelp += " ValidateSet: ($($Props.ValidateSet -join ","))" } if ($Props.ValidateScript.Length -gt 0) { $YAMLHelp += " ValidateScript: {$($Props.ValidateScript)}" } if ($YAMLHelp.Length -gt 0) { $Props.add("YAMLHelp",$YAMLHelp.Trim()) } $ScriptParameters += New-Object -TypeName PSObject -Property $Props } if ($ScriptParameters.count -eq 0) { Write-Warning "No parameters found for $ScriptPath. Make sure comment based help is correctly entered: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help?view=powershell-7.2" } # Build the YAMLParameters object containing more YAML specific information (could be done in previous loop... to do someday) $YAMLParameters = @() $ScriptArguments = "" foreach ($param in $ScriptParameters) { $ParamType = $ParamDefaultValue = $null # There are really only 3 parameter types we can use when running Powershell in a pipeline switch ($Param.Type.Name ) { "SwitchParameter" {$ParamType = "boolean"} {$_ -match"Int|Int32|long|byte|double|single"} {$ParamType = "number"} default {$ParamType = "string"} # Undeclared parameters will be of type Object and treated as string } # Not a proper switch, but this is where we figure out the correct default value switch ($Param.DefaultValue) { {$_ -match "\$"} {$ParamDefaultValue = "'' # Scriptet default: $($Param.DefaultValue)" ; break} # If default value contains $ it most likely references another parameter. {(-not ([string]::IsNullOrWhiteSpace($Param.DefaultValue)) -and $ParamType -eq "String")} {$ParamDefaultValue = "'$($param.defaultValue)'" ; break} # Add single quotes around string values {$ParamType -eq "number"} {$ParamDefaultValue = "$($param.defaultValue)" ; break} # No quotes around numbers as that would make it a string {$ParamType -eq "boolean"} {if ($param.defaultvalue -eq $true) {$ParamDefaultValue = "true"} else {$ParamDefaultValue = "false"} ; break} # Set a default value for booleans as well default {$ParamDefaultValue = "''"} # If all else fails, set the default value to empty string } $YAMLParameterProps = @{"Name" = $Param.name "YAMLHelp" = $Param.YAMLHelp "Type" = $ParamType "Default" = $ParamDefaultValue "ValidateSet" = $param.validateSet "VariableName" = "#ENV#_$($VariablePrefix)_$($param.name)" # Property to use as variable name in YAML. #ENV# will be replaced with the different environments to deploy to } $YAMLParameters += New-Object -TypeName PSObject -Property $YAMLParameterProps # Define the scriptarguments to pass to the script. The name of the variable will correspond with the name of the parameter if ($ParamType -eq "boolean") { $ScriptArguments += ("-$($Param.Name):`$`${{parameters.$($Param.name)}} ") # Add additional $ to turn "false" into "$false" } elseif ($param.type.name -eq "String") { $ScriptArguments += ("-$($Param.Name) '`${{parameters.$($Param.name)}}' ") # Make sure string values has single quotes around them so spaces and special characters survive } else { #integer type $ScriptArguments += ("-$($Param.Name) `${{parameters.$($Param.name)}} ") # Numbers as-is } } # Initialize PipelineVariables and set the corresponding template as comment $PipelineVariables = @() $PipelineVariables += " # $(Split-Path -Path $TemplateFilename -leaf)" # Default template parameters independent of script parameters $TemplateParameters = @() $TemplateParameters += " - name: serviceConnection # The name of the service Connection to use" $TemplateParameters += " type: string" $TemplateParameters += " default: false" # Build the template parameters foreach ($param in $YAMLParameters) { $TemplateParameters += "" $TemplateParameters += " - name: $($param.Name) # $($Param.YAMLHelp)" $TemplateParameters += " type: $($Param.type)" $TemplateParameters += " default: $($Param.Default)" if ($param.validateset) { $TemplateParameters += " values:" foreach ($value in $param.validateset) { if ($param.Type -eq "number") { $TemplateParameters += " - $value" } else { $TemplateParameters += " - '$value'" } } } $PipelineVariables += " $($Param.VariableName): $($param.Default) # $($Param.YAMLHelp)" } #region BuildTemplate $Template = @() $Template += "# Template to deploy $($ScriptFile.Name):" # Add script synopsis to template file if available if ($ScriptHelp.Synopsis) { if ($ScriptHelp.Synopsis.length -gt 0) { $Template += "# Synopsis:" $TempLine = ($ScriptHelp.Synopsis | foreach-object {$_ | Out-String} ).split("`r`n") foreach ($line in $TempLine) { $Template += "#`t" + $line } } } # Add script description to template file if available if ($ScriptHelp.description) { if ($ScriptHelp.description[0].Text.length -gt 0) { $Template += "# Description:" $TempLine = ($ScriptHelp.description | foreach-object {$_ | Out-String} ) -split("`r`n") foreach ($line in $TempLine) { $Template += "#`t" + $line } } } # Add script notes to template file if available if ($Scripthelp.Alertset) { if ($ScriptHelp.alertset.alert[0].Text.length -gt 0) { $Template += "# Notes:" $TempLine = ($ScriptHelp.alertset.alert[0] | foreach-object {$_ | Out-String} ).split("`r`n") | ForEach-Object {if ( -not [string]::isnullorwhitespace($_)) {$_}} foreach ($line in $TempLine) { $Template += "#`t" + $line } } } $Template += "" $Template += "parameters:" $Template += $TemplateParameters $Template += "" $Template += "steps:" $Template += " - task: AzurePowerShell@5" $Template += " displayName: ""PS: $($ScriptFile.Name)""" $Template += " inputs:" $Template += ' azureSubscription: "${{parameters.serviceConnection}}"' $Template += ' scriptType: "FilePath"' $Template += " scriptPath: ""$ScriptRelativePath"" # Relative to repo root" $Template += " azurePowerShellVersion: latestVersion" $Template += " scriptArguments: $ScriptArguments" if ($PowerShell7) { $Template += " pwsh: true # Run in PowerShell 7" } else { $Template += " pwsh: false # Run in PowerShell 7" } #endregion #BuildTemplate #region BuildPipeline $Pipeline = @() $Pipeline += "# Pipeline to deploy $(Split-Path -Path $TemplateFileName -Leaf)" $Pipeline += "" $Pipeline += "trigger: none" $Pipeline += "" $Pipeline += $PipelineVariablesHeader $Pipeline += "variables:" $Pipeline += " # Pipeline variables" $Pipeline += " deployment: '$((($DeploymentDisplayName.ToCharArray() | Where-Object {$_ -match '[\w| ]'}) -join '').replace(" ","_").tolower())' # Name of deployment" $Pipeline += " deploymentDisplayName: '$DeploymentDisplayName' # Name of deployment" foreach ($environ in $Environment) { $Pipeline += " $($environ)_ServiceConnection: '$($environ)_$($ServiceConnectionSuffix)' # Name of DevOps connection to use for $environ environment" $Pipeline += " $($environ)_EnvironmentName: '$environ'" } if (-not $PipelineVariablesToFile) { $Pipeline += $PipelineVariables[0] foreach ($e in $environment) { foreach ($var in $PipelineVariables[1..10000]) { $Pipeline += $var -Replace("#ENV#",$e) } } } else { $Pipeline += "" $Pipeline += "# Variable file locations:" foreach ($e in $environment) { $Pipeline += "# $($e): $($RelativePipelineVariablesFileNames[$e])" } } $Pipeline += "" $Pipeline += "pool:" $Pipeline += " vmImage: windows-latest" $Pipeline += "" $Pipeline += "stages:" foreach ($e in $Environment) { $Pipeline += "" $Pipeline += "# $e" $Pipeline += " - stage: `${{variables.$($e)_EnvironmentName}}" $Pipeline += " displayName: '`${{variables.$($e)_EnvironmentName}}'" if ($PipelineVariablesToFile) { $Pipeline += " variables:" $Pipeline += " - template: $($RelativePipelineVariablesFileNames[$e])" } $Pipeline += " jobs:" $Pipeline += " - deployment: `${{variables.deployment}}" $Pipeline += " displayName: `${{variables.deploymentDisplayName}}" $Pipeline += " environment: `${{variables.$($e)_EnvironmentName}}" $Pipeline += " strategy:" $Pipeline += " runOnce: #rolling, canary are the other strategies that are supported" $Pipeline += " deploy:" $Pipeline += " steps:" $Pipeline += " - checkout: self" $Pipeline += " - template: ""/$TemplateRelativePath"" #Template paths should be relative to the file that does the including. For specific path use /path/to/template" $Pipeline += " parameters:" $Pipeline += " serviceConnection: `$`{{variables.$($e)_ServiceConnection}}" foreach ($param in $YAMLParameters) { $ParamValue = "`${{variables.$($param.VariableName)}}" -replace "#ENV#",$e $Pipeline += " $($param.Name): $ParamValue" } } #endregion BuildPipeline #Finally output the files try { if ($PipelineVariablesToFile) { foreach ($e in $Environment) { $PipelineVariablesHeader += "variables:" for ($x = 0 ; $x -lt $PipelineVariables.count ; $x++) { $PipelineVariables[$x] = $PipelineVariables[$x] -replace("#ENV#",$e) } $PipelineVariables = $PipelineVariablesHeader + $PipelineVariables $PipelineVariables | Out-File -FilePath $PipelineVariablesFileNames[$e] -Encoding utf8 -NoClobber:(-not $Overwrite) -Force:$Overwrite } } $Pipeline | Out-File -FilePath $PipelineFileName -Encoding utf8 -NoClobber:(-not $Overwrite) -Force:$Overwrite $Template | Out-File -FilePath $TemplateFileName -Encoding utf8 -NoClobber:(-not $Overwrite) -Force:$Overwrite } catch { Write-Warning "Unable to write YAML files" Write-Warning $_.Exception.Message } } Function Remove-OldModules { [ CMDLetbinding()] Param () $Modules = Get-ChildItem "$($ENV:ProgramFiles)\WindowsPowerShell\Modules" | Where-Object {$_.mode -match "^d"} foreach ($Module in $Modules) { Write-Host "Removing old versions of $($Module.Name)" $Versions = Get-ChildItem -Path $Module.FullName | Sort-Object -Property Name -Descending $LastVersion = $Versions | Select-Object -First 1 $OldVersions = @($Versions | Where-Object {$_ -ne $LastVersion}) Remove-Module -Name $Module.Name -Force -Confirm:$false -ErrorAction SilentlyContinue if ($OldVersions.count -gt 0) { Write-Host "`t$($OldVersions.Count) old versions found" try { $OldVersions | Remove-Item -Recurse -Force -ErrorAction Stop Write-Host "`tRemoved" } catch { Write-Warning "`tCouldn't remove old versions: $($_.Exception.Message)" } } else { Write-Host "`tNo old versions found" } } } Function Set-AzTestSetup { [ CMDLetbinding()] param( [Parameter(Mandatory =$true)] [String[]]$ResourceGroupName, [string]$Prefix, [int]$NumWinVMs, [int]$NumLinuxVMs, [string]$VMAutoshutdownTime, [string]$WorkspaceName, [string]$AutomationAccountName, [String]$Location, [string]$KeyvaultName, [switch]$Force, [switch]$Remove ) foreach ($RG in $ResourceGroupName) { if (-not $PSBoundParameters.ContainsKey("Prefix")) {[string]$Prefix = $RG} if (-not $PSBoundParameters.ContainsKey("NumWinVMs")) {[int]$NumWinVMs = 2} if (-not $PSBoundParameters.ContainsKey("NumLinuxVMs")) {[int]$NumLinuxVMs = 0} if (-not $PSBoundParameters.ContainsKey("VMAutoshutdownTime")) {[string]$VMAutoshutdownTime = "2300"} if (-not $PSBoundParameters.ContainsKey("WorkspaceName")) {[string]$WorkspaceName = ($Prefix + "-workspace")} if (-not $PSBoundParameters.ContainsKey("AutomationAccountName")) {[string]$AutomationAccountName = ($Prefix + "-automation")} if (-not $PSBoundParameters.ContainsKey("Location")) {[String]$Location = "westeurope"} if (-not $PSBoundParameters.ContainsKey("KeyvaultName")) {[string]$KeyvaultName = ($Prefix + "-keyvault")} try { if (Get-AzResourceGroup -Name $RG -ErrorAction SilentlyContinue) { Write-Host "$RG exist" if ($Force -or $Remove) { Write-Host "`tWill be deleted" $WorkSpace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $RG -Name $WorkspaceName -ErrorAction SilentlyContinue $Keyvault = Get-AzKeyVault -VaultName $KeyvaultName -ResourceGroupName $RG -ErrorAction SilentlyContinue if ($null -eq $Keyvault) { $keyvault = Get-AzKeyVault -VaultName $KeyvaultName -InRemovedState -Location $location if ($null -ne $Keyvault) { Write-Host "`tDeleting $KeyvaultName" $null = Remove-AzKeyVault -VaultName $KeyvaultName -InRemovedState -Force -Confirm:$false -Location $Location } } else { Write-Host "`tDeleting $KeyvaultName" Remove-AzKeyVault -VaultName $KeyvaultName -Force -Confirm:$false -Location $location Start-Sleep -Seconds 1 Remove-AzKeyVault -VaultName $KeyvaultName -InRemovedState -Force -Confirm:$false -Location $location } if ($WorkSpace) { Write-Host "`tDeleting Workspace" $workspace | Remove-AzOperationalInsightsWorkspace -ForceDelete -Force -Confirm:$false } Write-Host "`tDeleting Resourcegroup and contained resources" Remove-AzResourceGroup -Name $RG -Force -Confirm:$false } else { Write-Host "Nothing to do" continue } } if ($Remove) { Write-Host "Remove specified. Exiting" continue } Write-Host "Creating $RG" New-AzResourceGroup -Name $RG -Location $Location Write-Host "Creating $AutomationAccountName" New-AzAutomationAccount -ResourceGroupName $RG -Name $AutomationAccountName -Location $Location -Plan Basic -AssignSystemIdentity Write-Host "Creating $KeyvaultName" New-AzKeyVault -Name $KeyvaultName -ResourceGroupName $RG -Location $Location -EnabledForDeployment -EnabledForTemplateDeployment -EnabledForDiskEncryption -SoftDeleteRetentionInDays 7 -Sku Standard Set-AzKeyVaultAccessPolicy -VaultName $KeyvaultName -ResourceGroupName $RG -UserPrincipalName "robert.eriksen_itera.com#EXT#@roedomlan.onmicrosoft.com" -PermissionsToKeys all -PermissionsToSecrets all -PermissionsToCertificates all -PermissionsToStorage all -Confirm:$false Set-AzKeyVaultAccessPolicy -VaultName $KeyvaultName -ResourceGroupName $RG -ObjectId (Get-AzAutomationAccount -ResourceGroupName $RG -Name $AutomationAccountName).Identity.PrincipalId -PermissionsToKeys all -PermissionsToSecrets all -PermissionsToCertificates all -PermissionsToStorage all -Confirm:$false Set-AzKeyVaultAccessPolicy -VaultName $KeyvaultName -ResourceGroupName $RG -ServicePrincipalName 04e7eb7d-da63-4c13-b5ba-04331145fdff -PermissionsToKeys all -PermissionsToSecrets all -PermissionsToCertificates all -PermissionsToStorage all -Confirm:$false Write-Host "Creating $WorkspaceName" New-azOperationalInsightsWorkspace -ResourceGroupName $RG -Name $WorkspaceName -Location $location -Sku pergb2018 $VMCredentials = [pscredential]::new("roe",("Pokemon1234!" | ConvertTo-SecureString -AsPlainText -Force)) # https://www.powershellgallery.com/packages/HannelsToolBox/1.4.0/Content/Functions%5CEnable-AzureVMAutoShutdown.ps1 $ShutdownPolicy = @{} $ShutdownPolicy.Add('status', 'Enabled') $ShutdownPolicy.Add('taskType', 'ComputeVmShutdownTask') $ShutdownPolicy.Add('dailyRecurrence', @{'time'= "$VMAutoshutdownTime"}) $ShutdownPolicy.Add('timeZoneId', "Romance Standard Time") $ShutdownPolicy.Add('notificationSettings', @{status='enabled'; timeInMinutes=30; emailRecipient="robert.eriksen@itera.com" }) $VMPrefix = "$($RG[0])$($RG[-1])" if ($NumWinVMs -gt 0) { (1..$NumWinVMs) | ForEach-Object { $VMName = ([string]"$($VMPrefix)-Win-$( $_)") Write-Host "Deploying $VMName" $null = New-AzVm -ResourceGroupName $RG -Name $VMName -Location $Location -Credential $VMCredentials -VirtualNetworkName "$($RG)-vnet" -SubnetName "$($RG)-Subnet" -SecurityGroupName "$($RG)-nsg" -PublicIpAddressName "$($VMName)-Public-ip" -OpenPorts 80,3389 -Size "Standard_DS1_v2" -Image Win2019Datacenter $vm = Get-AzVM -ResourceGroupName $RG -Name $VMName $rgName = $vm.ResourceGroupName $vmName = $vm.Name $location = $vm.Location $VMResourceId = $VM.Id $SubscriptionId = ($vm.Id).Split('/')[2] $ScheduledShutdownResourceId = "/subscriptions/$SubscriptionId/resourceGroups/$rgName/providers/microsoft.devtestlab/schedules/shutdown-computevm-$vmName" if ($VMAutoshutdownTime -ne "Off") { Write-Host "Setting autoshutdown: $VMAutoshutdownTime" $ShutdownPolicy['targetResourceId'] = $VMResourceId $null = New-azResource -Location $location -ResourceId $ScheduledShutdownResourceId -Properties $ShutdownPolicy -Force } } } if ($NumLinuxVMs -gt 0) { (1..$NumLinuxVMs) | ForEach-Object { $VMName = ([string]"$($VMPrefix)-Lin-$($_)") Write-Host "Deploying $VMName" $null = New-AzVm -ResourceGroupName $RG -Name $VMName -Location $Location -Credential $VMCredentials -VirtualNetworkName "$($RG)-vnet" -SubnetName "$($RG)-Subnet" -SecurityGroupName "$($RG)-nsg" -PublicIpAddressName "$($VMName)-Public-ip" -OpenPorts 80,3389 -Size "Standard_DS1_v2" -Image UbuntuLTS $vm = Get-AzVM -ResourceGroupName $RG -Name $VMName $rgName = $vm.ResourceGroupName $vmName = $vm.Name $location = $vm.Location $VMResourceId = $VM.Id $SubscriptionId = ($vm.Id).Split('/')[2] $ScheduledShutdownResourceId = "/subscriptions/$SubscriptionId/resourceGroups/$rgName/providers/microsoft.devtestlab/schedules/shutdown-computevm-$vmName" if ($VMAutoshutdownTime -ne "Off") { Write-Host "Setting autoshutdown: $VMAutoshutdownTime" $ShutdownPolicy['targetResourceId'] = $VMResourceId $null = New-azResource -Location $location -ResourceId $ScheduledShutdownResourceId -Properties $ShutdownPolicy -Force } } } } catch { throw $_ } } } Function Set-SystemVariables { #Collect all variables and store them, so userdefined variables can be easily cleared without restarting the PowerShell session New-Variable -Name 'SysVars' -Scope 'Global' -Force $global:SysVars = Get-Variable | Select-Object -ExpandProperty Name $global:SysVars += 'SysVars' } |