Script Hangs in Windows 10 While Accessing Azure Key Vault Secrets, Works in Windows 11: Need Assistance

57 Views Asked by At

I’m working on a script where it fetches the details of secrets from an Azure Key Vault and recreates the secrets into another Key Vault of a different tenant. I'm using a Windows Form to provide a GUI to select the required secret. The script works fine in Windows 11, but when attempting to execute it in Windows 10, the script sometimes gets stuck. It appears to be hanging at line number 183 in the Get-AzKeyvaultSecret cmdlet. This issue occurs occasionally, and I couldn't understand why it's happening. Below are the PowerShell versions:

  1. Windows 10: PowerShell version: 5.1.19041.3031

  2. Windows 11: PowerShell version: 5.1.22621.2506

Any help will be highly appreciated. Screen shot: enter image description here

Code:

    # Load Windows Forms
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Load configuration from JSON file
Try {
    $configFilePath = "D:\AxKVMigrationInfo\config.json"
    $config = Get-Content $configFilePath -ErrorAction Stop | ConvertFrom-Json 
}
Catch {
    Write-Host "[ERROR] Config file not found: $_"
    exit 1
}

# Set log file path from the configuration
$logFilePath = $config.FilePaths.Log

# Subscription details for Source Tenant
$sourceSubscriptionId = $config.SourceTenant.SubscriptionId
$sourceVaultName = $config.SourceTenant.VaultName

# Subscription details for Target Tenant
$targetSubscriptionId = $config.TargetTenant.SubscriptionId
$targetVaultName = $config.TargetTenant.VaultName

# CSV file path where migrated secrets will get exported
$logDirectory = Split-Path -Path $LogFilePath -Parent
$CSVFilePath = Join-Path -Path $logDirectory -ChildPath "Migrated_Secrets.csv"


# Function to log messages
function Write-Log {
    param (
        [string]$message,
        [string]$logType
    )

    $logMessage = "$logType - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $message"
    Add-Content -Path $logFilePath -Value $logMessage
}

# Function to check and install requried modules
function Import-RequiredModule {
    Try {
        Import-Module -Name Az.KeyVault -ErrorAction Stop
        Import-Module -Name Az.Accounts -ErrorAction Stop
    }
    catch { 
        Write-Host "The required PowerShell module is not found on the machine. Installing the modules Az.KeyVault and Az.Accounts." 
        Write-Log "The required PowerShell module is not found on the machine. Installing the modules Az.KeyVault and Az.Accounts." "[INFO]"

        Install-Module -Name Az.KeyVault
        Install-Module -Name Az.Accounts
        
        Import-Module -Name Az.KeyVault
        Import-Module -Name Az.Accounts
        Write-Host "Az.KeyVault and Az.Accounts successfully installed and imported" 
        Write-Log "Az.KeyVault and Az.Accounts successfully installed and imported" "[INFO]"
    }
}

# Function to prompt the user to enter tenant credentials
function Prompt-ForCredentials {
    param (
        [string]$account
    )

    $title = "Enter $account Tenant Credentials"
    $message = "Please enter the credentials for $account Tenant."

    [System.Windows.Forms.MessageBox]::Show($message, $title, [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
}


# Function to connect to Azure account and log connection event
function Connect-ToAzureAccount {
    param (
        [string]$SubscriptionId,
        [string]$Account
    )

    Write-Host "Enter $Account Tenant credentials"
    # Prompt user for credentials
    Prompt-ForCredentials -account $Account

    # Connect to Azure account
    try {
        Connect-AzAccount -Subscription $SubscriptionId -ErrorAction Stop
        
        # Get the current user
        $currentUser = (Get-AzContext).Account.Id
        Write-Log "Connected to $Account Azure account. User: $currentUser" "[INFO]"
    }
    catch {
        Write-Log "Error connecting to $Account Azure account: $_" "[ERROR]"
        throw "Error connecting to $Account Azure account."
    }
}

# Function to disconnect from Azure account and log disconnection event
function Disconnect-FromAzureAccount {
    param (
        [string]$Account
    )

    # Disconnect from Azure account
    try {
        Disconnect-AzAccount -ErrorAction Stop
        Write-Log "Disconnected from $Account Azure account" "[INFO]"
    }
    catch {
        Write-Log "Error disconnecting from $Account Azure account: $_" "[ERROR]"
        throw "Error disconnecting from $Account Azure account."
    }
}

# Function to display a confirmation message box
function Show-ConfirmationBox {
    param (
        [string]$message
    )

    return [System.Windows.Forms.MessageBox]::Show(
        $message,
        "Confirmation",
        [System.Windows.Forms.MessageBoxButtons]::OKCancel,
        [System.Windows.Forms.MessageBoxIcon]::Warning
    )
}


# Function to export specific secrets from source Key Vault based on CSV file
function Export-SpecificSecretsFromSourceKeyVault {
    param (
        [string]$SourceVaultName,
        [string]$SourceSubscriptionId,
        [string[]]$ArrSelectedSecretName
    )

    # Update the minimum and maximum of the progress bar.
    $ProgressBar.Minimum = 0
    $ProgressBar.Maximum = $ArrSelectedSecretName.Count

    # Update the text of the Progress label.
    $progressLable.Text = "Exporting....."

    Write-Host "Exporting specific secrets from source Key Vault ($SourceVaultName) in Subscription ($SourceSubscriptionId)"
    Write-Log "Exporting specific secrets from source Key Vault ($SourceVaultName) in Subscription ($SourceSubscriptionId)" "[INFO]"

    # Export specific secrets based on Selected Items
    $SpecificSecretDetailsExport = foreach ($secretName in $ArrSelectedSecretName) {
        
        # update progress status:
        $ProgressBar.Value = $ProgressBarValue
        
        try {
            Get-AzKeyVaultSecret -VaultName $SourceVaultName -Name $secretName -ErrorAction Stop
        }
        catch {
            Write-Log "Error exporting secret '$secretName' from source Key Vault: $_" "[ERROR]"
        }
        $ProgressBarValue++
    }

    # Update the text of the Progress label.
    $progressLable.Text = "Exported"

    Write-Host "Exporting Successful."
    Write-Log "Exporting Successful." "[INFO]"

    Disconnect-FromAzureAccount -Account "Source"

    return $SpecificSecretDetailsExport
}


# Function to export migrated or imported secrets to CSV
function Export-SecretsToCSV {
    param (
        [string]$CSVFilePath,
        [PSCustomObject]$SecretDetail
    )

    # Extract specific details for each secret
    $exportData = [PSCustomObject]@{
        SecretName     = $SecretDetail.Name
        ContentType    = $SecretDetail.ContentType
        ActivationDate = $SecretDetail.NotBefore
        ExpiryDate     = $SecretDetail.Expires
        State          = $SecretDetail.Enabled
    }

    $exportData | Export-Csv -Path $CSVFilePath -NoTypeInformation -Append
    Write-Log "Secrets exported to CSV file: $CSVFilePath" "[INFO]"
}



# Function to import secrets into target Key Vault
function Import-SecretsIntoTargetKeyVault {
    param (
        [string]$TargetVaultName,
        [string]$TargetSubscriptionId,
        [Array]$SecretDetails
    )

    Connect-ToAzureAccount -SubscriptionId $TargetSubscriptionId -Account "Target"

    # Update the minimum and maximum of the progress bar.
    $ProgressBar.Minimum = 0
    $ProgressBar.Maximum = $SecretDetails.Count

    # Update the text of the Progress label.
    $progressLable.Text = "Importing....."

    Write-Log "Importing secrets into target Key Vault ($TargetVaultName) in Subscription ($TargetSubscriptionId)." "[INFO]"

    # Core logic
    foreach ($SD in $SecretDetails) {
        $SecretName = $SD.Name

        # update progress status:
        $ProgressBar.Value = $ProgressBarValue

        if ($SecretName -ne $null) {
            # Check if the secret already exists in the target Key Vault
            $existingSecret = Get-AzKeyVaultSecret -VaultName $TargetVaultName -Name $SecretName -ErrorAction SilentlyContinue
            if ($existingSecret -eq $null) {
                # Create secret into the Target Tenant
                try {
                    Set-AzKeyVaultSecret -VaultName $TargetVaultName -Name $SecretName -SecretValue $SD.SecretValue -Expires $SD.Expires -NotBefore $SD.NotBefore -ContentType $SD.ContentType -Tag $SD.Tags -ErrorAction Stop
                    
                    Write-Log "Secret '$SecretName' imported successfully." "[SUCCESS]"

                    # Call Export-SecretsToCSV function after importing secrets into the target Key Vault
                    Export-SecretsToCSV -CSVFilePath $CSVFilePath -SecretDetail $SD
                    
                }
                catch {
                    Write-Log "Error importing secret '$SecretName' into target Key Vault: $_" "[ERROR]"
                }
            }
            else {
                Write-Log "Secret '$SecretName' already exists in the target Key Vault. Skipping." "[WARNING]"
            }
        }
    
        $ProgressBarValue++
    
    }

    # Update the text of the Progress label.
    $progressLable.Text = "Imported"

    Disconnect-FromAzureAccount -Account "Target"

    Write-Log "Import completed successfully." "[INFO]"
}


# Define minimum form size
$minimumFormSize = New-Object System.Drawing.Size(500, 550)

# Function to dynamically adjust the size of $checkedListBox based on form size
function Resize-CheckedListBox {
    $checkedListBox.Size = New-Object System.Drawing.Size(($form.ClientSize.Width - 30), ($form.ClientSize.Height - 150))
    $buttonSelectAll.Location = New-Object System.Drawing.Point(10, ($form.ClientSize.Height - 120))
    $buttonDeselectAll.Location = New-Object System.Drawing.Point(170, ($form.ClientSize.Height - 120))
    $buttonMigrate.Location = New-Object System.Drawing.Point(($form.ClientSize.Width - $buttonMigrate.Width - 30), ($form.ClientSize.Height - 120))
    $progressBar.Location = New-Object System.Drawing.Point(10, ($form.ClientSize.Height - 60))
    $progressLable.Location = New-Object System.Drawing.Point(350, ($form.ClientSize.Height - 60))
}

# Function to enforce minimum form size
function Enforce-MinimumFormSize {
    if ($form.ClientSize.Width -lt $minimumFormSize.Width -or $form.ClientSize.Height -lt $minimumFormSize.Height) {
        $form.ClientSize = $minimumFormSize
    }
}

# Function to enable or disable the migration button based on selection
function Update-MigrationButton {
    if ($checkedListBox.CheckedItems.Count -gt 0) {
        $buttonMigrate.Enabled = $true
    }
    else {
        $buttonMigrate.Enabled = $false
    }
}

# Create Form
$form = New-Object System.Windows.Forms.Form
$form.Text = "AzKV Secret Migration Tool"
$form.Size = $minimumFormSize  # Set initial form size to minimum
$form.StartPosition = "CenterScreen"

# Attach Resize event handler to form
$form.add_Resize({
        Resize-CheckedListBox
        Enforce-MinimumFormSize
    })

# Attach Load event handler to form to ensure minimum size is enforced initially
$form.add_Load({
        Enforce-MinimumFormSize
    })

# Create CheckedListBox
$checkedListBox = New-Object System.Windows.Forms.CheckedListBox
$checkedListBox.Location = New-Object System.Drawing.Point(10, 10)
$checkedListBox.Font = New-Object System.Drawing.Font("Arial", 12)  # Adjust the font size as needed
$checkedListBox.ItemHeight = 30  # Adjust the height as needed

# Create Button for Select All
$buttonSelectAll = New-Object System.Windows.Forms.Button
$buttonSelectAll.Size = New-Object System.Drawing.Size(150, 30)
$buttonSelectAll.Location = New-Object System.Drawing.Point(10, ($form.ClientSize.Height - 120))
$buttonSelectAll.Text = "Select All"

# Add Click event to the Select All Button
$buttonSelectAll.Add_Click({
        # Check all checkboxes
        for ($i = 0; $i -lt $checkedListBox.Items.Count; $i++) {
            $checkedListBox.SetItemChecked($i, $true)

            # Update migration button status
            Update-MigrationButton
        }
    })

# Create Button for Deselect All
$buttonDeselectAll = New-Object System.Windows.Forms.Button
$buttonDeselectAll.Size = New-Object System.Drawing.Size(150, 30)
$buttonDeselectAll.Location = New-Object System.Drawing.Point(170, ($form.ClientSize.Height - 120))
$buttonDeselectAll.Text = "Deselect All"

# Add Click event to the Deselect All Button
$buttonDeselectAll.Add_Click({
        # Uncheck all checkboxes
        for ($i = 0; $i -lt $checkedListBox.Items.Count; $i++) {
            $checkedListBox.SetItemChecked($i, $false)

            # Update migration button status
            Update-MigrationButton
        }
    })

# Create Button for Migrate
$buttonMigrate = New-Object System.Windows.Forms.Button
$buttonMigrate.Size = New-Object System.Drawing.Size(150, 30)
$buttonMigrate.Text = "Migrate Secrets"
$buttonMigrate.Enabled = $false  # Initially disable the button


# Import required Module
Import-RequiredModule


# Get secrets from source Key Vault
Connect-ToAzureAccount -SubscriptionId $SourceSubscriptionId -Account "Source"
Try {
    $secrets = Get-AzKeyVaultSecret -VaultName $sourceVaultName
}
catch {
    Write-Log "Error Listing secret '$secretName' from source Key Vault: $_" "[ERROR]"
    exit 1
}


# Populate CheckedListBox with secret names
$secrets | ForEach-Object {
    [void]$checkedListBox.Items.Add($_.Name)
}

# Progress Bar
$progressBar = New-Object System.Windows.Forms.ProgressBar
$progressBar.Style = "Continuous"
$progressBar.Width = 310
$progressBar.Height = 25
$progressBar.Location = New-Object System.Drawing.Point(10, ($form.ClientSize.Height - 60))
$ProgressBarValue = 1

# Progress bar label
$progressLable = New-Object System.Windows.Forms.Label
$progressLable.AutoSize = $true
$progressLable.Width = 100
$progressLable.Height = 25
$progressLable.Location = New-Object System.Drawing.Point(350, ($form.ClientSize.Height - 60))




# Add Click event to the Button
$buttonMigrate.Add_Click({
        # Display confirmation message box
        $confirmation = Show-ConfirmationBox "The selected secrets will be Imported into the target tenant. If you want to migrate click on OK"
        

        # Check if the user clicked OK
        if ($confirmation -eq [System.Windows.Forms.DialogResult]::OK) {
            # Get selected secrets
            $selectedSecrets = Export-SpecificSecretsFromSourceKeyVault -SourceVaultName $sourceVaultName -SourceSubscriptionId $sourceSubscriptionId -ArrSelectedSecretName $checkedListBox.CheckedItems
            
            # Proceed with migration
            Import-SecretsIntoTargetKeyVault -TargetVaultName $targetVaultName -TargetSubscriptionId $targetSubscriptionId -SecretDetails $selectedSecrets
            
            # Show Import successful message
            [System.Windows.Forms.MessageBox]::Show("Imported successfully! For more details, check the logs file.", "Import Successful", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)

            $form.Close()
        }
        
    })


# Add controls to the form
$form.Controls.AddRange(@($checkedListBox, $buttonSelectAll, $buttonDeselectAll, $buttonMigrate, $progressBar, $progressLable))

# Add Change event to the checkedListBox to update migration button status
$checkedListBox.Add_SelectedIndexChanged({ Update-MigrationButton })

# Call Resize-CheckedListBox to set initial sizes and positions
Resize-CheckedListBox

# Show the form
[void]$form.ShowDialog()

# Display a message indicating the completion of the script
Write-Log "Key Vault migration script completed successfully." "[INFO]"

# Display the log file path
Write-Host "Log file exported to: $logFilePath"

# Display the log file path
Write-Host "CSV file exported to: $CSVFilePath"

# Remove all variables for no longer access.
Remove-Variable * -ErrorAction SilentlyContinue

I have tried the following steps:

  • Attempted with different versions of the Az.KeyVault module; however, the script still gets stuck.
  • Tried adding a sleep value between faching the deatils from Azure key vault, but it also didn't work.
  • Tested without GUI, and it worked fine.

I am expecting that, the script should work with windows 10 as it's working in windows 11.

1

There are 1 best solutions below

0
Jahnavi On

Check the PowerShell version and Windows 10 are compatible with each other to execute the script. Execute $PSVersionTable as shown below.

Check keyvault module versions and import the module with the required version in the below format to avoid conflicts.

Import-Module Az.KeyVault -RequiredVersion xx

enter image description here

Add debugging points to your script to ensure and check where exactly it is failing.

Check the dependencies of the code GUI and their versions as they might impact the code to not work as expected.

Try to restart the current PS Session window and execute the script again. Sometimes restarting the window will work in these kind of issues.

Reference 1, 2 for more related information.