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. .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 ) $ErrorActionPreference = "Stop" try { if ($PSCmdlet.ShouldProcess((Get-CmAzSubscriptionName), "Deploy Cloudmarque Core Automation Stack")) { # Initializing settings file values $azSubscription = Get-AzSubscription $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" } else { Write-Error "Set type to be either runbook or dsc" Break } $location = $SettingsObject.Location Write-Verbose "Location set to $location" Write-Verbose "creating automation account for $accountType" if (!$location) { Write-Error "Location not found" Break } $keyVaultName = $SettingsObject.Automation.$accountType.KeyVaultName if (!$keyVaultName) { Write-Error "Provide Key Vault For Certificate" break } $keyVault = Get-AzKeyVault -VaultName $keyVaultName if (!$keyVault) { Write-Error "Key Vault $keyVaultName doesnt exist. Please provide existing Key Vault Name" Break } $certificateName = $SettingsObject.Automation.$accountType.CertificateName if (!$certificateName) { Write-Information "No KeyVault Certificate Provided.. A new certificate will be created" $certificateName = $SettingsObject.Automation.$accountType.CertificateName } $keyVaultCertificatePassword = $SettingsObject.Automation.$accountType.KeyVaultCertificatePassword if (!$keyVaultCertificatePassword) { Write-Error "No keyVault path or Password provided for Certificate...." break } 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 $keyVaultName -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" Write-Verbose "SCM Intergration will be skipped ... " } $nameResourceGroup = Get-CmAzResourceName -Resource "ResourceGroup" -Architecture "Core" -Region $location -Name $SettingsObject.name $resourceGroup = New-AzResourceGroup -ResourceGroupName $nameResourceGroup -Location $location -Force Write-Verbose "Resource Group Created : $nameResourceGroup" # Creating Automation accountname using name generator $automationAccountName = Get-CmAzResourceName -Resource "Automation" -Architecture "Core" -Region $location -Name $SettingsObject.name # Create Automation account New-AzResourceGroupDeployment ` -ResourceGroupName $nameResourceGroup ` -TemplateFile "$PSScriptRoot\New-CmAzCoreAutomation.json" ` -AccountName $automationAccountName ` -Location $location ` -Force 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()) Write-Verbose "Fetch Certificate Data from Keyvault..Done!" $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) } Write-Verbose "Requires Application administrator or GLOBAL ADMIN" Write-Verbose "Trying to create Application Credential" New-AzADAppCredential -ApplicationId $application.ApplicationId -CertValue $pfxCert -StartDate $Certificate.NotBefore -EndDate $Certificate.NotAfter Write-Verbose "Application credential is created." # Requires Application administrator or GLOBAL ADMIN Write-Verbose "Checking for 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 } Write-Verbose "Service principal created." } else { Write-Verbose "Service principal found." } # Requires User Access Administrator or Owner. Write-Verbose "working on getting role assignment" $getRole = Get-AzRoleAssignment -ServicePrincipalName $application.ApplicationId -ErrorAction SilentlyContinue if (!$getRole) { Write-Verbose "Role Assignment doesnt exist. Trying to create one" $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 ) Write-Verbose "Trying to Pull Certificate Password from KeyVault" $certificatePassword = Get-AzKeyVaultSecret -VaultName $keyVaultName -Name $keyVaultCertificatePassword Write-Verbose "Trying to Pull Certificate from KeyVault" $pfxFileByte = [System.Convert]::FromBase64String((Get-AzKeyVaultSecret -VaultName $keyVaultName -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 $protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $certificatePassword.SecretValueText) [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.SecretValue -Exportable Write-Verbose "Key Vault Certificate ported to Automation Certificate Asset successfully" $certificateVaulesOutput = @{ "certificatePath" = $PfxCertPathForRunAsAccount; "password" = $certificatePassword.SecretValueText } $certificateVaulesOutput } 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 Write-Verbose "Automation connection $ConnectionAssetName created successfully" } [string]$applicationDisplayName = "AutomationAppAccount-$automationAccountName" # Mandatory Azure Default Vaules - DO NOT CHANGE [string]$connectionAssetName = "AzureRunAsConnection" [string]$connectionTypeName = "AzureServicePrincipal" Write-Verbose "Get Certificate from Keyvault" $keyVaultSelfSignedCert = (Get-AzKeyVaultCertificate -VaultName $keyVaultName -Name $certificateName).Certificate Write-Verbose "Create New Certificate using keyvault If doesnot exist" if (!$keyVaultSelfSignedCert) { try { Write-Verbose "Certificate doesnot exist..." Write-Verbose "Creating new certificate in keyvault: $keyVaultName" $keyVaultSelfSignedCert = KeyVaultSelfSignedCert ` -KeyVault $keyVaultName ` -CertificateName $certificateName ` -SubjectName "CN=$certificateName" ` -ValidityInMonths 12 ` -RenewDaysBefore 30 Write-Verbose "Certificate generated $certificateName : $($keyVaultSelfSignedCert.Thumbprint)" $keyVaultSelfSignedCert = (Get-AzKeyVaultCertificate -VaultName $keyVaultName -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 $keyVaultSelfSignedPfxCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($certificateValues.certificatePath , $certificateValues.password) Write-Verbose "Create a service principal" $servicePrincipal = CreateServicePrincipal -Certificate $keyVaultSelfSignedPfxCert -ApplicationDisplayName $applicationDisplayName Write-Verbose "Populate the ConnectionFieldValues" $thumbprint = $keyVaultSelfSignedPfxCert.Thumbprint $connectionFieldValues = @{"ApplicationId" = $servicePrincipal.ApplicationId.ToString(); "TenantId" = $azSubscription.TenantId; "CertificateThumbprint" = $thumbprint; "SubscriptionId" = $azSubscription.SubscriptionId } 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 $connectionTypeName -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.ToString)" } } Write-Verbose "Finished!" } } } catch { $PSCmdlet.ThrowTerminatingError($PSitem); } } |