Public/Core/automation/New-CmAzCoreAutomation.ps1
function New-CmAzCoreAutomation { <# .Synopsis Create an Automation account with runbooks. .Description Completes the following: * Creates Resource Group for runbook, dsc or both. * Creates Automation account for runbook, dsc or both. * Creates Key vault certificate if not available. * Create RunAsAccount and RunAsCertificate for Automation accounts. * Optionally sync code repository (tvfc | git | github). .Parameter SettingsFile File path for the settings file to be converted into a settings object. .Parameter SettingsObject Object containing the configuration values required to run this cmdlet. .Parameter TagSettingsFile File path for the tags settings file containing tags defination. .Parameter AutomationCertificatePassword Certificate password used to create automation account run as certificate. .Component Core .Example New-CmAzCoreAutomation -SettingsFile "c:/directory/settingsFile.yml" .Example New-CmAzCoreAutomation -SettingsObject $settings #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium")] param( [parameter(Mandatory = $true, ParameterSetName = "Settings File")] [String]$SettingsFile, [parameter(Mandatory = $true, ParameterSetName = "Settings Object")] [Object]$SettingsObject, [AllowEmptyString()] [String]$TagSettingsFile, [SecureString]$AutomationCertificatePassword ) $ErrorActionPreference = "Stop" try { if ($PSCmdlet.ShouldProcess((Get-CmAzSubscriptionName), "Deploy Cloudmarque Core Automation Stack")) { # Initializing settings file values $azSubscription = Get-AzContext $projectContext = Get-CmAzContext -RequireAzure -ThrowIfUnavailable if ($SettingsFile -and !$SettingsObject) { $SettingsObject = Get-CmAzSettingsFile -Path $SettingsFile } elseif (!$SettingsFile -and !$SettingsObject) { Write-Error "No valid input settings." -Category InvalidArgument -CategoryTargetName "SettingsObject" } foreach ($item in $SettingsObject.Automation.Keys) { $randomValue = Get-Random -Minimum 1001 -Maximum 9999 # Set Account Type if ($item -eq "dsc") { $accountType = "dsc" } elseif ($item -eq "runbook") { $accountType = "runbook" $item = "rnb" } else { Write-Error "Set type to be either runbook or dsc." } if (!$SettingsObject.automation.$accountType){ $SettingsObject.automation.$accountType = @{} } Write-Verbose "Setting location set to $($SettingsObject.Location)..." $location = $SettingsObject.Location Write-Verbose "Creating automation account for $accountType..." if (!$location) { Write-Error "Location not found." } Set-GlobalServiceValues -GlobalServiceContainer $SettingsObject -ServiceKey "keyvault" -ResourceServiceContainer $SettingsObject.Automation.$accountType -IsDependency $keyVault = Get-CmAzService -Service $SettingsObject.Automation.$accountType.service.dependencies.keyvault -Region $SettingsObject.location -ThrowIfUnavailable $certificateName = $SettingsObject.Automation.$accountType.CertificateName if (!$certificateName) { Write-Verbose "No KeyVault Certificate secret provided. Default context name will be used. A new certificate will be created if required..." $certificateName = "Automation-$item-$($SettingsObject.Name)" } if (!$AutomationCertificatePassword){ $keyVaultCertificatePasswordSecretName = $SettingsObject.Automation.$accountType.keyVaultCertificatePasswordSecretName if (!$keyVaultCertificatePasswordSecretName) { Write-Error "No keyVault path or Password provided for Certificate..." break } $AutomationCertificatePassword = (Get-AzKeyVaultSecret -VaultName $keyVault.name -Name $keyVaultCertificatePasswordSecretName).SecretValue } if ($SettingsObject.Automation.$accountType.sourceControl.url) { $repoUrl = $SettingsObject.Automation.$accountType.sourceControl.url $repoType = $SettingsObject.Automation.$accountType.sourceControl.type $folderPath = $SettingsObject.Automation.$accountType.sourceControl.folderPath $keyVaultPersonalAccessToken = $SettingsObject.Automation.$accountType.sourceControl.keyVaultPersonalAccessToken $branch = $SettingsObject.Automation.$accountType.sourceControl.branch if (!$keyVaultPersonalAccessToken) { Write-Error "No keyVault path or Password provided for Personal Access Token." break } if (!$branch) { $branch = "master" } if (!$repoType) { $repoType = "github" } $repoType = $repoType.ToLower() if (!$folderPath) { $folderPath = "/" } $personalAccessToken = (Get-AzKeyVaultSecret -VaultName $keyVault.name -Name $keyVaultPersonalAccessToken).SecretValue if (!$personalAccessToken) { Write-Error "No PAT found on key vault." } Write-Verbose "Repository settings are found to be ok, SCM integration will be attempted..." } else { Write-Verbose "No Repository provided $accountType, SCM Intergration will be skipped..." } Write-Verbose "Creating Resource Group..." $nameResourceGroup = Get-CmAzResourceName -Resource "ResourceGroup" -Architecture "Core" -Region $location -Name "$item-$($SettingsObject.name)" $resourceGroupServiceTag = @{ "cm-service" = $SettingsObject.service.publish.resourceGroup } $resourceGroup = New-AzResourceGroup -ResourceGroupName $nameResourceGroup -Location $location -Tag $resourceGroupServiceTag -Force Write-Verbose "Creating Automation Account..." $automationAccountName = Get-CmAzResourceName -Resource "Automation" -Architecture "Core" -Region $location -Name "$item-$($SettingsObject.name)" # Create Automation account New-AzResourceGroupDeployment ` -ResourceGroupName $nameResourceGroup ` -TemplateFile "$PSScriptRoot\New-CmAzCoreAutomation.json" ` -AccountName $automationAccountName ` -Location $location ` -AutomationService $SettingsObject.service.publish.automation ` -Force > $Null function KeyVaultSelfSignedCert { param ( $keyVault, $CertificateName, $SubjectName, $ValidityInMonths, $RenewDaysBefore ) Write-Verbose "Starting Certificate Generation..." $policy = New-AzKeyVaultCertificatePolicy ` -SubjectName $SubjectName ` -ReuseKeyOnRenewal ` -IssuerName 'Self' ` -ValidityInMonths $ValidityInMonths ` -RenewAtNumberOfDaysBeforeExpiry $RenewDaysBefore $keyVaultCertificate = Add-AzKeyVaultCertificate ` -VaultName $keyVault ` -CertificatePolicy $policy ` -Name $CertificateName while ( $keyVaultCertificate.Status -ne "completed") { Start-Sleep -Seconds 1 $keyVaultCertificate = Get-AzKeyVaultCertificateOperation -VaultName $keyVault -Name $CertificateName } (Get-AzKeyVaultCertificate -VaultName $keyVault -Name $CertificateName).Certificate } function CreateServicePrincipal { param ( [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [string] $applicationDisplayName ) Write-Verbose "Fetch Certificate Data from Keyvault..." $pfxCert = [Convert]::ToBase64String($Certificate.GetRawCertData()) $application = Get-AzADApplication -DisplayName $applicationDisplayName -ErrorAction SilentlyContinue if (!$application) { Write-Verbose "Application not found. Creating new application..." $application = New-AzADApplication -DisplayName $applicationDisplayName -HomePage ("http://" + $applicationDisplayName) -IdentifierUris ("http://" + $randomValue) } #Needs Application administrator or GLOBAL ADMIN" Write-Verbose "Creating Application Credential..." New-AzADAppCredential -ApplicationId $application.ApplicationId -CertValue $pfxCert -StartDate $Certificate.NotBefore -EndDate $Certificate.NotAfter Write-Verbose "Checking for existing service principal..." $servicePrincipal = Get-AzADServicePrincipal -ApplicationId $application.ApplicationId if(!$servicePrincipal) { while (!$servicePrincipal) { Write-Verbose "Trying to create new service principal..." New-AzADServicePrincipal -ApplicationId $application.ApplicationId $servicePrincipal = Get-AzADServicePrincipal -ApplicationId $application.ApplicationId } } else { Write-Verbose "Service principal found..." } # Requires User Access Administrator or Owner. Write-Verbose "Fetching Role Assignment..." $getRole = Get-AzRoleAssignment -ServicePrincipalName $application.ApplicationId -ErrorAction SilentlyContinue if (!$getRole) { Write-Verbose "Role Assignment doesnt exist. Creating new Role Assignment..." $newRole = New-AzRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $application.ApplicationId -ErrorAction SilentlyContinue $retries = 0; While (!$newRole -and $retries -le 6) { Start-Sleep -s 10 New-AzRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $application.ApplicationId | Write-Verbose -ErrorAction SilentlyContinue $newRole = Get-AzRoleAssignment -ServicePrincipalName $application.ApplicationId -ErrorAction SilentlyContinue $retries++; } Write-Verbose "Contributer role assigned to Service Principal $($application.ApplicationId)..." } else { Write-Verbose "Role Assignment Already Present..." Write-Verbose $getRole } return $application; } function CreateAutomationCertificateAsset { param ( $certificateName, [SecureString]$certificatePassword ) Write-Verbose "Trying to Pull Certificate from KeyVault..." $pfxFileByte = [System.Convert]::FromBase64String((Get-AzKeyVaultSecret -VaultName $keyVault.name -Name $certificateName).SecretValueText) $pfxCertPathForRunAsAccount = Join-Path -Path $($projectContext.ProjectRoot) ($CertificateName + ".pfx") # Write to a file $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($pfxFileByte, "", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) #Export the .pfx file $certificatePasswordSecretValueText = $certificatePassword | ConvertFrom-SecureString -AsPlainText $certificatePasswordSecretValueText $protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $certificatePasswordSecretValueText) [System.IO.File]::WriteAllBytes($PfxCertPathForRunAsAccount, $protectedCertificateBytes) Write-Verbose "Creating Automation Certificate Asset..." Remove-AzAutomationCertificate -ResourceGroupName $nameResourceGroup -AutomationAccountName $automationAccountName -Name "AzureRunAsCertificate" -ErrorAction SilentlyContinue New-AzAutomationCertificate -ResourceGroupName $nameResourceGroup -AutomationAccountName $automationAccountName -Path $pfxCertPathForRunAsAccount -Name "AzureRunAsCertificate" -Password $certificatePassword -Exportable @{ "certificatePath" = $PfxCertPathForRunAsAccount; "password" = $certificatePasswordSecretValueText } } function CreateAutomationConnectionAsset { param ( [string] $ResourceGroup, [string] $AutomationAccountName, [string] $ConnectionAssetName, [string] $ConnectionTypeName, [System.Collections.Hashtable] $ConnectionFieldValues ) Write-Verbose "Creating Automation Connection Asset..." Remove-AzAutomationConnection -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccountName -Name $ConnectionAssetName -Force -ErrorAction SilentlyContinue New-AzAutomationConnection -ResourceGroupName $ResourceGroup -AutomationAccountName $AutomationAccountName -Name $ConnectionAssetName -ConnectionTypeName $ConnectionTypeName -ConnectionFieldValues $ConnectionFieldValues > $null } [string]$applicationDisplayName = "AutomationAppAccount-$automationAccountName" Write-Verbose "Fetching Certificate from Keyvault..." $keyVaultSelfSignedCert = (Get-AzKeyVaultCertificate -VaultName $keyVault.name -Name $certificateName).Certificate if (!$keyVaultSelfSignedCert) { try { Write-Verbose "Creating new certificate in keyvault: $($keyVault.name)..." $keyVaultSelfSignedCert = KeyVaultSelfSignedCert ` -KeyVault $keyVault.name ` -CertificateName $certificateName ` -SubjectName "CN=$certificateName" ` -ValidityInMonths 12 ` -RenewDaysBefore 30 Write-Verbose "Certificate generated $certificateName : $($keyVaultSelfSignedCert.Thumbprint)..." $keyVaultSelfSignedCert = (Get-AzKeyVaultCertificate -VaultName $keyVault.name -Name $certificateName).Certificate } catch { Write-Error "Certificate Generation failed." break } } else { Write-Verbose "Certificate found $certificateName : $($keyVaultSelfSignedCert.Thumbprint)..." } # Creates Automation Certificate $certificateValues = CreateAutomationCertificateAsset -CertificateName $certificateName -CertificatePassword $AutomationCertificatePassword $keyVaultSelfSignedPfxCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($certificateValues.certificatePath , $certificateValues.password) Write-Verbose "Creating Service Principal..." $servicePrincipal = CreateServicePrincipal -Certificate $keyVaultSelfSignedPfxCert -ApplicationDisplayName $applicationDisplayName Write-Verbose "Populating ConnectionFieldValues..." $connectionFieldValues = @{ "ApplicationId" = $servicePrincipal.ApplicationId.ToString(); "TenantId" = $azSubscription.Subscription.TenantId; "CertificateThumbprint" = $keyVaultSelfSignedPfxCert.Thumbprint; "SubscriptionId" = $azSubscription.Subscription.id } Write-Verbose "Create an Automation connection asset named AzureRunAsConnection in the Automation account. This connection uses the service principal." CreateAutomationConnectionAsset -ResourceGroup $nameResourceGroup -AutomationAccountName $automationAccountName -ConnectionAssetName "AzureRunAsConnection" -ConnectionTypeName "AzureServicePrincipal" -ConnectionFieldValues $connectionFieldValues if ($repoUrl) { try { if ($repoType -eq "tvfc") { New-AzAutomationSourceControl -Name "$repoType-$randomValue" -RepoUrl $repoUrl -SourceType VsoTfvc -AccessToken $personalAccessToken -ResourceGroupName $nameResourceGroup -AutomationAccountName $automationAccountName -FolderPath $folderPath } elseif ($repoType -eq "github") { New-AzAutomationSourceControl -Name "$repoType-$randomValue" -RepoUrl $repoUrl -SourceType GitHub -AccessToken $personalAccessToken -Branch $branch -ResourceGroupName $nameResourceGroup -AutomationAccountName $automationAccountName -FolderPath $folderPath } elseif ($repoType -eq "git") { New-AzAutomationSourceControl -Name "$repoType-$randomValue" -RepoUrl $repoUrl -SourceType VsoGit -AccessToken $personalAccessToken -Branch $branch -ResourceGroupName $nameResourceGroup -AutomationAccountName $automationAccountName -FolderPath $folderPath } else { Write-Warning "Please choose correct repository - tvfc | git | github." -ErrorAction Continue } Write-Verbose "$repoType Added..." # Attempt autosync try { Update-AzAutomationSourceControl -AutomationAccountName $automationAccountName -Name "$repoType-$randomValue" -AutoSync $true -ResourceGroupName $nameResourceGroup } catch { $_.ToString() | Write-Verbose Write-Warning "AutoSync couldn't be enabled. Make sure you have 'Read, query, manage' permissions" -ErrorAction Continue -WarningAction Continue } # Start first sync job Start-AzAutomationSourceControlSyncJob -AutomationAccountName $automationAccountName -Name "$repoType-$randomValue" -ResourceGroupName $nameResourceGroup Write-Verbose "First Sync initiated..." } catch { Write-Verbose "Repository couldn't be added..." Write-Verbose "$PSitem..." } } Set-DeployedResourceTags -TagSettingsFile $TagSettingsFile -ResourceGroupIds $nameResourceGroup } Write-Verbose "Finished!" } } catch { $PSCmdlet.ThrowTerminatingError($PSitem); } } |