Category: Azure

Azure Key Vault – Script for copying secrets from one to another

The following script can be used to copy secrets from one Key Vault to another.

You can use this to copy secrets or just the secret names (-NameOnly) from one Key Vault to another, in the same or another subscription.

It’s as simple as the following PowerShell commands below to execute:

$srcSubscriptionName="Development"
$srcKvName="my-keyvault-dev-ne"
$destSubscriptionName="Production"
$destKvName="my-keyvault-prod-ne"
.\Copy-KeyVault-Secrets.ps1 -SrcSubscriptionName $srcSubscriptionName -SrcKvName $srcKvName -DestSubscriptionName $destSubscriptionName -DestKvName $destKvName -NameOnly
<#PSScriptInfo
.VERSION 1.1
.GUName 48b4b27a-b77e-41e6-8a37-b3767da5caee
.AUTHOR Nicholas Rogoff

.RELEASENOTES
Initial version.
#>
<# 
.SYNOPSIS 
Copy Key Vault Secrets from one Vault to another. 
 
.DESCRIPTION 
Loops through all secrets and copies them or fills them with a 'Needs Configuration'.

PRE-REQUIREMENT
---------------
Run 'Import-Module Az.Accounts'
Run 'Import-Module Az.KeyVault'
You need to be logged into Azure and have the access necessary rights to both Key Vaults.

.INPUTS
None. You cannot pipe objects to this!

.OUTPUTS
None.

.PARAMETER SrcSubscriptionName
This is the Source Subscription Name

.PARAMETER SrcKvName
The name of the Source Key Vault

.PARAMETER DestSubscriptionName
This is the destination Subscription Name

.PARAMETER DestKvName
The name of the destination Key Vault

.PARAMETER NameOnly
Set to only copy across the secret name and NOT the actual secret. The secret will be populated with 'Needs Configuration'

.NOTES
  Version:        1.1
  Author:         Nicholas Rogoff
  Creation Date:  2021-08-09
  Purpose/Change: Refined for publication
   
.EXAMPLE 
PS> .\Copy-KeyVault-Secrets.ps1 -SrcSubscriptionName $srcSubscriptionName -SrcKvName $srcKvName -DestSubscriptionName $destSubscriptionName -DestKvName $destKvName -NameOnly
This will copy across only the secret names, filling the secret with 'Needs Configuration'
#>
#---------------------------------------------------------[Script Parameters]------------------------------------------------------
[CmdletBinding()]
Param(
  [Parameter(Mandatory = $true, HelpMessage = "This is the Source Subscription Name")]
  [string] $SrcSubscriptionName,
  [Parameter(Mandatory = $true, HelpMessage = "The name of the Source Key Vault")]
  [string] $SrcKvName,
  [Parameter(Mandatory = $false, HelpMessage = "This is the destination Subscription Name. If not set or blank then same subscription is assumed")]
  [string] $DestSubscriptionName,
  [Parameter(Mandatory = $true, HelpMessage = "The name of the destination Key Vault")]
  [string] $DestKvName,
  [Parameter(Mandatory = $false, HelpMessage = "Only copy across the secret name and NOT the actual secret. The secret will be populated with 'Needs Configuration'")]
  [switch] $NameOnly
)

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
Write-Host ($(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff') + " Starting copying from " + $SrcKvName + " to " + $DestKvName + "... ") -ForegroundColor Blue

# Set Error Action to Silently Continue
$ErrorActionPreference = 'Continue'

#----------------------------------------------------------[Declarations]----------------------------------------------------------
# Any Global Declarations go here

#----------------------------------------------------------[Functions]----------------------------------------------------------


#-----------------------------------------------------------[Execution]------------------------------------------------------------

$success = 0
$failed = 0

# ensure source subscription is selected
Select-AzSubscription -Subscription $SrcSubscriptionName

$Tags = @{ 'Migrated' = 'true'; 'Source Key Vault' = $SrcKvName }

$sourceSecrets = Get-AzKeyVaultSecret -VaultName $SrcKvName
if ($DestSubscriptionName) {
  #Need to switch subscriptions
  Select-AzSubscription -Subscription $DestSubscriptionName
}

ForEach ($sourceSecret in $sourceSecrets) {
  $Error.clear()

  $name = $sourceSecret.Name
  $tags = $sourceSecret.Tags
  $secret = Get-AzKeyVaultSecret -VaultName $srckvName -Name $name


  Write-Host "Adding SecretName: $name ..."
  if ($NameOnly) {
    $value = ConvertTo-SecureString 'Needs Configuration' -AsPlainText -Force
  }
  else {
    $value = $secret.SecretValue
  }
  $secret = Set-AzKeyVaultSecret -VaultName $destkvName -Name $sourceSecret.Name -SecretValue $value -ContentType $sourceSecret.ContentType -Tags $tags 
  
  if (!$Error[0]) {
    $success += 1
  }
  else {
    $failed += 1
    Write-Error "!! Failed to copy secret $name"
  }
}

Write-Output "================================="
Write-Output "Completed Key Vault Secrets Copy"
Write-Output "Succeeded: $success"
Write-Output "Failed: $failed"
Write-Output "================================="

Reboot all the VM’s in a Windows Virtual Desktop Host Pool…safely ;-)

I have found that the session hosts often end up reporting a status of ‘Needs Assistance’. This can be caused by updates having been applied that require a reboot to complete…and other unknown issues. Often a reboot will sort them out. So I developed a simple script to assist.

The following script will allow you to specifiy the only the VM’s in a Host Pool and only those that ‘Need Assistance’ and that have no active sessions on too….or not!

Save the full script to a file called RebootHosts.ps1

<#PSScriptInfo
.VERSION 1.0
.GUID b053571a-b9f4-445d-ac05-45e184cf6f90
.AUTHOR Nicholas Rogoff
.RELEASENOTES

#>
<#
.SYNOPSIS
  Reboots VMs in a HostPool
.DESCRIPTION
  This will iterate through the VMs registered to a host pool and reboot them. 
.NOTES
  Version:        1.0
  Author:         Nicholas Rogoff
  Creation Date:  2020-09-03
  Purpose/Change: Initial script development

.EXAMPLE
  .\RebootHosts.ps1 -HostPoolName "my-host-pool" -HostPoolResourceGroupName "my-host-pool-rg" -OnlyDoIfNeedsAssistance -SkipIfActiveSessions
#>

#---------------------------------------------------------[Script Parameters]------------------------------------------------------
[CmdletBinding()]
Param (
    #Script parameters go here
    [Parameter(mandatory = $true)]
    [string]$HostPoolName,

    [Parameter(mandatory = $true)]
    [string]$HostPoolResourceGroupName,
    
    [Parameter(mandatory = $false)]
    [switch]$SkipIfActiveSessions,

    [Parameter(mandatory = $false)]
    [switch]$OnlyDoIfNeedsAssistance
)

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

#Set Error Action to Silently Continue
$ErrorActionPreference = 'SilentlyContinue'

#----------------------------------------------------------[Declarations]----------------------------------------------------------

#Any Global Declarations go here

#-----------------------------------------------------------[Functions]------------------------------------------------------------


#-----------------------------------------------------------[Execution]------------------------------------------------------------

Write-Output "Starting to Enable Boot Diagnostics for VMs in Host Pool $HostPoolName ..."
if ($OnlyDoIfNeedsAttention) {
    Write-Output "!! Only hosts flagged as 'Needs Assistance' will be rebooted !!"
}
if ($SkipIfActiveSessions) {
    Write-Output "!! Only hosts with zero sessions will be rebooted !!"
}

$rebooted = 0
$skippedSessions = 0
$skippedOK = 0
$shutdown = 0

$sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $HostPoolResourceGroupName -HostPoolName $HostPoolName
foreach ($sh in $sessionHosts) {
    

    # Name is in the format 'host-pool-name/vmname.domainfqdn' so need to split the last part
    $VMName = $sh.Name.Split("/")[1]
    $VMName = $VMName.Split(".")[0]
    
    $Session = $sh.Session
    $Status = $sh.Status
    $UpdateState = $sh.UpdateState
    $UpdateErrorMessage = $sh.UpdateErrorMessage

    Write-output "=== Starting Reboot for VM: $VMName"
    Write-output "Session: $Session"
    Write-output "Status: $Status"
    Write-output "UpdateState: $UpdateState"
    Write-output "UpdateErrorMessage: $UpdateErrorMessage"

    if ($Status -ne "Unavailable") {
        if ($Status -ne "NeedsAssistance" -and $OnlyDoIfNeedsAssistance ) {
            $skippedOK += 1
            Write-output "!! The VM '$VMName' is not in 'Needs Assistance' state, so will NOT be rebooted. !!"       
        }
        elseif ($SkipIfActiveSessions -and $Session -gt 0) {
            $skippeSessions += 1
            Write-output "!! The VM '$VMName' has $Session session(s), so will NOT be rebooted. !!"       
        }
        else {
            $rebooted += 1
            Restart-AzVM -ResourceGroupName $HostPoolResourceGroupName -Name $VMName
            Write-output "=== Reboot initiated for VM: $VMName"       
        }
    }
    else {
        $shutdown += 1
        Write-output "!! The VM '$VMName' must be started in order to reboot it. !!"       
    }

}

Write-Output ""
Write-Output "============== Completed =========================="
Write-Output "Skipped due Not needing attention: $skippedOK"
Write-Output "Skipped due to active sessions: $skippedSessions"
Write-Output "Host not started: $shutdown"
Write-Output "Rebooted: $rebooted"
Write-Output "==================================================="

You will need to log in to Azure and select your subscription in the normal way using:

Login-AzAccount
Select-AzSubscription "my-subscription"

You can then simply run the script as follows:

.\RebootHosts.ps1 -HostPoolName "my-host-pool" -HostPoolResourceGroupName "my-host-pool-rg" -OnlyDoIfNeedsAssistance -SkipIfActiveSessions

Cloud Resource Naming Convention (Azure)

In any organisation it is important to get a standard naming convention in place for most things, but especially with cloud-based resources.

As many types of cloud resources require globally unique names (due to platform DNS resolution), it’s important to have a strategy that will give you a good chance of achieving global uniqueness, but also as helpful as possible to human beings, as well as codifiable in DevOps CD pipelines.

Continue reading “Cloud Resource Naming Convention (Azure)”
‘*** Execution Timeout Expired’ with SqlPackage.exe on Azure DevOps Release Pipeline Fix

‘*** Execution Timeout Expired’ with SqlPackage.exe on Azure DevOps Release Pipeline Fix

A while ago I was having an issue deploying a SQL Data Tools project using the Azure DevOps pipelines. It worked fine when there was no data in the database, but once there was some data to preserve in the release the DACPAC deployment started to timeout with the following error when running SqlPackage.

Continue reading “‘*** Execution Timeout Expired’ with SqlPackage.exe on Azure DevOps Release Pipeline Fix”

Resource Tag management in Microsoft Azure

Adding tags to resources in Azure is generally a good idea. This helps administrators manage billing, knowing what things are and when they can be safely decommissioned etc..

I have includes here some guidance and useful scripts for adding tags and managing tags on resources and resource groups.

Continue reading “Resource Tag management in Microsoft Azure”