Files
Jurjen Ladenius a226ca97ac added documetation
2025-11-03 08:12:01 +01:00

546 lines
30 KiB
PowerShell
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
Generates a comprehensive inventory of Azure Web Apps and deployment slots across all management groups and subscriptions.
.DESCRIPTION
This script enumerates all Azure Web Apps and their deployment slots within active subscriptions
across the entire Azure tenant, collecting detailed configuration properties, security settings,
governance tags, and deployment information. The results are exported to a timestamped CSV file
for analysis, compliance reporting, and security auditing.
Key capabilities:
- Multi-tenant Web App discovery across all management groups
- Deployment slot enumeration and configuration analysis
- Security configuration analysis (HTTPS, TLS versions, debugging, FTPS)
- Last deployment date tracking via Azure Management API
- Governance tag extraction for team ownership and compliance tracking
- Identity configuration analysis (system/user-assigned managed identity)
- Timestamped CSV export for audit trails and trend analysis
The script processes both production Web Apps and their deployment slots, providing
comprehensive visibility into the web application estate including security posture,
deployment practices, and governance compliance.
.PARAMETER None
This script does not accept parameters and will process all Web Apps across all accessible subscriptions.
Note: Visual Studio subscriptions are automatically excluded from processing.
.OUTPUTS
CSV File: "<date> azure_webapps.csv"
Contains columns for:
- Resource identification (ID, name, type, kind, location, state)
- Management hierarchy (management group, subscription, resource group)
- Governance tags (team, product, environment, data classification)
- Security configuration (HTTPS, TLS version, debugging, FTPS state)
- Runtime configuration (PHP version, HTTP/2.0 support)
- Identity configuration (managed identity type)
- Deployment tracking (last successful deployment date)
.EXAMPLE
.\WebApps.ps1
Discovers all Web Apps and deployment slots, generates:
"2024-10-30 1435 azure_webapps.csv"
.NOTES
File Name : WebApps.ps1
Author : Cloud Engineering Team
Prerequisite : Azure PowerShell module (Az.Websites, Az.Resources, Az.Accounts)
Copyright : (c) 2024 Effectory. All rights reserved.
Version History:
1.0 - Initial release with comprehensive Web App inventory functionality
.LINK
https://docs.microsoft.com/en-us/azure/app-service/
https://docs.microsoft.com/en-us/powershell/module/az.websites/
.COMPONENT
Requires Azure PowerShell modules:
- Az.Websites (for Web App enumeration and configuration retrieval)
- Az.Resources (for resource group and management group access)
- Az.Accounts (for authentication and access token management)
.ROLE
Required Azure permissions:
- Website Contributor or Reader on all App Service resources
- Management Group Reader for organizational hierarchy access
- Reader access on target subscriptions for deployment API calls
.FUNCTIONALITY
- Multi-subscription Web App discovery and slot enumeration
- Security configuration analysis and compliance checking
- Deployment tracking via Azure Management REST API
- Identity configuration analysis and managed identity reporting
- CSV export with comprehensive web application metadata
#>
#Requires -Modules Az.Websites, Az.Resources, Az.Accounts
#Requires -Version 5.1
[CmdletBinding()]
param()
# Uncomment the following line if authentication is required
#Connect-AzAccount
<#
.SYNOPSIS
Retrieves the last successful deployment date for an Azure Web App or deployment slot.
.DESCRIPTION
This function calls the Azure Management REST API to retrieve deployment history
and extract the last successful deployment timestamp for a Web App or slot.
.PARAMETER siteName
The name of the Azure Web App.
.PARAMETER resourceGroupName
The name of the resource group containing the Web App.
.PARAMETER subscriptionId
The subscription ID containing the Web App.
.PARAMETER slotName
Optional. The name of the deployment slot. If not provided, queries the production slot.
.OUTPUTS
String. The last successful deployment end time, or empty string if no deployments found.
#>
function GetDeployment {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string] $siteName,
[Parameter(Mandatory = $true)]
[string] $resourceGroupName,
[Parameter(Mandatory = $true)]
[string] $subscriptionId,
[Parameter(Mandatory = $false)]
[string] $slotName = ""
)
try {
# Get current Azure context for tenant ID
$context = Get-AzContext
if (-not $context) {
Write-Warning "No Azure context found for deployment API call"
return ""
}
# Get access token for Azure Management API
$accessTokenInfo = Get-AzAccessToken -TenantId $context.Tenant.Id
$access_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($accessTokenInfo.Token))
# Build API URL for deployments
if ($slotName -ne "") {
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/slots/$slotName/deployments?api-version=2022-03-01"
} else {
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/deployments?api-version=2022-03-01"
}
# Make API call to get deployment history
$headers = @{ Authorization = "Bearer $access_token" }
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $headers -ErrorAction SilentlyContinue
# Extract last successful deployment date
if ($response -and $response.value -and $response.value.Length -gt 0) {
$lastDeployment = $response.value[0]
if ($lastDeployment.properties.last_success_end_time) {
return $lastDeployment.properties.last_success_end_time
}
}
return ""
} catch {
Write-Warning "Error retrieving deployment info for $siteName`: $($_.Exception.Message)"
return ""
}
}
class ResourceCheck {
[string] $ResourceId = ""
[string] $Kind = ""
[string] $Location = ""
[string] $ResourceName = ""
[string] $ResourceGroup = ""
[string] $ResourceType = ""
[string] $State = ""
[string] $ManagementGroupId = ""
[string] $ManagementGroupName = ""
[string] $SubscriptionId = ""
[string] $SubscriptionName = ""
[string] $Tag_Team = ""
[string] $Tag_Product = ""
[string] $Tag_Environment = ""
[string] $Tag_Data = ""
[string] $Tag_Deployment = ""
[string] $Tag_CreatedOnDate = ""
[string] $Prop_HttpsOnly = ""
[string] $Prop_PhpVersion = ""
[string] $Prop_RemoteDebuggingEnabled = ""
[string] $Prop_MinTlsVersion = ""
[string] $Prop_FtpsState = ""
[string] $Prop_Http20Enabled = ""
[string] $Prop_Identity = ""
[string] $LastDeployDate = ""
}
# Initialize script execution
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$startTime = Get-Date
Write-Host "======================================================================================================================================================================"
Write-Host "🌐 AZURE WEB APPS INVENTORY GENERATOR" -ForegroundColor Cyan
Write-Host "======================================================================================================================================================================"
Write-Host "⏰ Started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Green
Write-Host ""
try {
# Validate Azure authentication
$context = Get-AzContext
if (-not $context) {
throw "No Azure context found. Please run Connect-AzAccount first."
}
Write-Host "🔐 Authenticated as: $($context.Account.Id)" -ForegroundColor Green
Write-Host "🏢 Tenant: $($context.Tenant.Id)" -ForegroundColor Green
Write-Host ""
# Initialize output file and tracking variables
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date azure_webapps.csv"
Write-Host "📄 Output file: $fileName" -ForegroundColor Yellow
Write-Host ""
# Initialize counters for progress tracking
$totalWebApps = 0
$totalSlots = 0
$processedManagementGroups = 0
$processedSubscriptions = 0
$securityIssues = @()
$deploymentTrackingErrors = 0
# Get management groups for organizational structure
Write-Host "🏗️ Discovering management group structure..." -ForegroundColor Cyan
$managementGroups = Get-AzManagementGroup
Write-Host "✅ Found $($managementGroups.Count) management groups" -ForegroundColor Green
Write-Host ""
# Process each management group
foreach ($managementGroup in $managementGroups) {
$processedManagementGroups++
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
Write-Host "🏗️ Management Group [$($managementGroup.DisplayName)]" -ForegroundColor Cyan
Write-Host " ID: $($managementGroup.Id)" -ForegroundColor DarkGray
try {
# Get active non-Visual Studio subscriptions in this management group
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name |
Where-Object State -eq "Active" |
Where-Object DisplayName -NotLike "Visual Studio*"
Write-Host " 📋 Found $($subscriptions.Count) active subscriptions (excluding Visual Studio)" -ForegroundColor Green
foreach ($subscription in $subscriptions) {
$processedSubscriptions++
Write-Host ""
Write-Host " 🔄 Processing Subscription: $($subscription.DisplayName)" -ForegroundColor Yellow
try {
# Extract subscription ID and set context
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
$subscriptionId = $scope.Replace("/subscriptions/", "")
Write-Host " ID: $subscriptionId" -ForegroundColor DarkGray
Set-AzContext -SubscriptionId $subscriptionId | Out-Null
# Get all resource groups in the subscription
$allResourceGroups = Get-AzResourceGroup
[ResourceCheck[]]$Result = @()
$subscriptionWebApps = 0
$subscriptionSlots = 0
foreach ($group in $allResourceGroups) {
Write-Host " 📁 Resource Group: $($group.ResourceGroupName)" -ForegroundColor DarkCyan -NoNewline
try {
# Get Web Apps in this resource group
$allWebApps = Get-AzWebApp -ResourceGroupName $group.ResourceGroupName -ErrorAction SilentlyContinue
if ($allWebApps.Count -gt 0) {
Write-Host " - Found $($allWebApps.Count) Web Apps" -ForegroundColor Green
$subscriptionWebApps += $allWebApps.Count
} else {
Write-Host " - No Web Apps" -ForegroundColor DarkGray
}
foreach ($webApp in $allWebApps) {
Write-Host " 🌐 Web App: $($webApp.Name)" -ForegroundColor White
try {
# Analyze security configuration
if (-not $webApp.HttpsOnly) {
$securityIssues += "🔓 HTTPS not enforced: $($webApp.Name) in $($group.ResourceGroupName)"
}
if ($webApp.SiteConfig.MinTlsVersion -lt "1.2") {
$securityIssues += "⚠️ TLS version below 1.2: $($webApp.Name) (version: $($webApp.SiteConfig.MinTlsVersion))"
}
if ($webApp.SiteConfig.RemoteDebuggingEnabled) {
$securityIssues += "🐛 Remote debugging enabled: $($webApp.Name) in $($group.ResourceGroupName)"
}
if ($webApp.SiteConfig.FtpsState -eq "AllAllowed") {
$securityIssues += "📂 FTPS allows unencrypted connections: $($webApp.Name) in $($group.ResourceGroupName)"
}
# Create resource check object for Web App
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.ResourceId = $webApp.Id
$resourceCheck.Kind = $webApp.Kind
$resourceCheck.Location = $webApp.Location
$resourceCheck.State = $webApp.State
$resourceCheck.ResourceName = $webApp.Name
$resourceCheck.ResourceGroup = $webApp.ResourceGroup
$resourceCheck.ResourceType = $webApp.Type
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscriptionId
$resourceCheck.SubscriptionName = $subscription.DisplayName
$resourceCheck.Tag_Team = $webApp.Tags.team
$resourceCheck.Tag_Product = $webApp.Tags.product
$resourceCheck.Tag_Environment = $webApp.Tags.environment
$resourceCheck.Tag_Data = $webApp.Tags.data
$resourceCheck.Tag_CreatedOnDate = $webApp.Tags.CreatedOnDate
$resourceCheck.Tag_Deployment = $webApp.Tags.drp_deployment
$resourceCheck.Prop_HttpsOnly = $webApp.HttpsOnly
$resourceCheck.Prop_PhpVersion = $webApp.SiteConfig.PhpVersion
$resourceCheck.Prop_RemoteDebuggingEnabled = $webApp.SiteConfig.RemoteDebuggingEnabled
$resourceCheck.Prop_MinTlsVersion = $webApp.SiteConfig.MinTlsVersion
$resourceCheck.Prop_FtpsState = $webApp.SiteConfig.FtpsState
$resourceCheck.Prop_Http20Enabled = $webApp.SiteConfig.Http20Enabled
$resourceCheck.Prop_Identity = $webApp.Identity.Type
# Get deployment information with error handling
$deploymentDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId
if ([string]::IsNullOrEmpty($deploymentDate)) {
$deploymentTrackingErrors++
}
$resourceCheck.LastDeployDate = $deploymentDate
$Result += $resourceCheck
$totalWebApps++
# Process deployment slots
try {
$allSlots = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup -ErrorAction SilentlyContinue
if ($allSlots.Count -gt 0) {
Write-Host " 🔄 Found $($allSlots.Count) deployment slots" -ForegroundColor Cyan
$subscriptionSlots += $allSlots.Count
}
foreach ($slotTemp in $allSlots) {
try {
Write-Host " 📍 Slot: $($slotTemp.Name)" -ForegroundColor DarkCyan
[string] $slotName = $slotTemp.Name.Split("/")[1]
$slot = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup -Slot $slotName
# Analyze slot security configuration
if (-not $slot.HttpsOnly) {
$securityIssues += "🔓 HTTPS not enforced on slot: $($slot.Name) in $($webApp.ResourceGroup)"
}
if ($slot.SiteConfig.MinTlsVersion -lt "1.2") {
$securityIssues += "⚠️ TLS version below 1.2 on slot: $($slot.Name) (version: $($slot.SiteConfig.MinTlsVersion))"
}
if ($slot.SiteConfig.RemoteDebuggingEnabled) {
$securityIssues += "🐛 Remote debugging enabled on slot: $($slot.Name) in $($webApp.ResourceGroup)"
}
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
$resourceCheck.ResourceId = $slot.Id
$resourceCheck.Kind = $slot.Kind
$resourceCheck.Location = $slot.Location
$resourceCheck.State = $slot.State
$resourceCheck.ResourceName = $slot.Name
$resourceCheck.ResourceGroup = $slot.ResourceGroup
$resourceCheck.ResourceType = $slot.Type
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscriptionId
$resourceCheck.SubscriptionName = $subscription.DisplayName
$resourceCheck.Tag_Team = $slot.Tags.team
$resourceCheck.Tag_Product = $slot.Tags.product
$resourceCheck.Tag_Environment = $slot.Tags.environment
$resourceCheck.Tag_Data = $slot.Tags.data
$resourceCheck.Tag_CreatedOnDate = $slot.Tags.CreatedOnDate
$resourceCheck.Tag_Deployment = $slot.Tags.drp_deployment
$resourceCheck.Prop_HttpsOnly = $slot.HttpsOnly
$resourceCheck.Prop_PhpVersion = $slot.SiteConfig.PhpVersion
$resourceCheck.Prop_RemoteDebuggingEnabled = $slot.SiteConfig.RemoteDebuggingEnabled
$resourceCheck.Prop_MinTlsVersion = $slot.SiteConfig.MinTlsVersion
$resourceCheck.Prop_FtpsState = $slot.SiteConfig.FtpsState
$resourceCheck.Prop_Http20Enabled = $slot.SiteConfig.Http20Enabled
$resourceCheck.Prop_Identity = $slot.Identity.Type
# Get deployment information for slot
$slotDeploymentDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId -slotName $slotName
if ([string]::IsNullOrEmpty($slotDeploymentDate)) {
$deploymentTrackingErrors++
}
$resourceCheck.LastDeployDate = $slotDeploymentDate
$Result += $resourceCheck
$totalSlots++
} catch {
Write-Host " ❌ Error processing slot '$($slotTemp.Name)': $($_.Exception.Message)" -ForegroundColor Red
}
}
} catch {
Write-Host " ❌ Error getting slots for '$($webApp.Name)': $($_.Exception.Message)" -ForegroundColor Red
}
} catch {
Write-Host " ❌ Error processing Web App '$($webApp.Name)': $($_.Exception.Message)" -ForegroundColor Red
}
}
} catch {
Write-Host " - ❌ Error accessing resource group: $($_.Exception.Message)" -ForegroundColor Red
}
}
# Export results for this subscription
if ($Result.Count -gt 0) {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
Write-Host " ✅ Exported $($Result.Count) Web App resources from subscription" -ForegroundColor Green
Write-Host " Web Apps: $subscriptionWebApps, Deployment Slots: $subscriptionSlots" -ForegroundColor DarkGray
} else {
Write-Host " No Web Apps found in subscription" -ForegroundColor DarkYellow
}
} catch {
Write-Host " ❌ Error processing subscription: $($_.Exception.Message)" -ForegroundColor Red
}
}
} catch {
Write-Host " ❌ Error accessing management group: $($_.Exception.Message)" -ForegroundColor Red
}
Write-Host ""
}
# Calculate execution time and generate comprehensive summary report
$endTime = Get-Date
$executionTime = $endTime - $startTime
Write-Host "======================================================================================================================================================================"
Write-Host "📊 AZURE WEB APPS INVENTORY SUMMARY" -ForegroundColor Cyan
Write-Host "======================================================================================================================================================================"
Write-Host "⏰ Execution Time: $($executionTime.ToString('hh\:mm\:ss'))" -ForegroundColor Green
Write-Host "🏗️ Management Groups Processed: $processedManagementGroups" -ForegroundColor Green
Write-Host "📋 Subscriptions Processed: $processedSubscriptions" -ForegroundColor Green
Write-Host "🌐 Total Web Apps Discovered: $totalWebApps" -ForegroundColor Green
Write-Host "🔄 Total Deployment Slots: $totalSlots" -ForegroundColor Cyan
Write-Host "📄 Results Exported To: $fileName" -ForegroundColor Yellow
if (Test-Path $fileName) {
$fileSize = (Get-Item $fileName).Length
Write-Host "💾 File Size: $([math]::Round($fileSize/1KB, 2)) KB" -ForegroundColor Green
}
# Display deployment tracking statistics
if ($deploymentTrackingErrors -gt 0) {
Write-Host "⚠️ Deployment Tracking Issues: $deploymentTrackingErrors Web Apps/slots" -ForegroundColor Yellow
Write-Host " (This may be due to API permissions or apps without deployment history)" -ForegroundColor DarkGray
} else {
Write-Host "✅ Deployment Tracking: Successfully retrieved for all Web Apps" -ForegroundColor Green
}
# Display security analysis summary
if ($securityIssues.Count -gt 0) {
Write-Host ""
Write-Host "🚨 SECURITY ANALYSIS SUMMARY" -ForegroundColor Red
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
Write-Host "Found $($securityIssues.Count) potential security concerns:" -ForegroundColor Yellow
foreach ($issue in $securityIssues | Select-Object -First 15) {
Write-Host " $issue" -ForegroundColor Yellow
}
if ($securityIssues.Count -gt 15) {
Write-Host " ... and $($securityIssues.Count - 15) more issues (see CSV for complete details)" -ForegroundColor DarkYellow
}
Write-Host ""
Write-Host "📋 Security Recommendations:" -ForegroundColor Cyan
Write-Host " • Enforce HTTPS-only access on all Web Apps and slots" -ForegroundColor White
Write-Host " • Upgrade minimum TLS version to 1.2 or higher" -ForegroundColor White
Write-Host " • Disable remote debugging on production Web Apps" -ForegroundColor White
Write-Host " • Configure FTPS to require SSL/TLS (disable 'AllAllowed')" -ForegroundColor White
Write-Host " • Enable managed identities for secure Azure service authentication" -ForegroundColor White
} else {
Write-Host ""
Write-Host "✅ SECURITY ANALYSIS: No major security concerns detected" -ForegroundColor Green
}
# Calculate and display Web App statistics
$totalWebAppResources = $totalWebApps + $totalSlots
$averageSlotsPerApp = if ($totalWebApps -gt 0) { [math]::Round($totalSlots / $totalWebApps, 1) } else { 0 }
if ($totalWebApps -gt 0) {
Write-Host ""
Write-Host "📈 WEB APP DEPLOYMENT ANALYSIS:" -ForegroundColor Cyan
Write-Host " Total Web App Resources: $totalWebAppResources (Apps + Slots)" -ForegroundColor White
Write-Host " Average Deployment Slots per App: $averageSlotsPerApp" -ForegroundColor White
if ($averageSlotsPerApp -gt 1) {
Write-Host " 🔄 High Slot Usage: Good deployment strategy with staging/testing slots" -ForegroundColor Green
} elseif ($averageSlotsPerApp -gt 0.5) {
Write-Host " 📊 Moderate Slot Usage: Some apps using deployment slots" -ForegroundColor Yellow
} else {
Write-Host " 💡 Low Slot Usage: Consider implementing deployment slots for safer deployments" -ForegroundColor White
}
}
Write-Host ""
Write-Host "📈 NEXT STEPS:" -ForegroundColor Cyan
Write-Host " 1. Review the generated CSV file for detailed Web App configurations" -ForegroundColor White
Write-Host " 2. Address security recommendations identified above" -ForegroundColor White
Write-Host " 3. Analyze deployment patterns and slot usage for optimization" -ForegroundColor White
Write-Host " 4. Implement monitoring and alerting for critical Web Apps" -ForegroundColor White
Write-Host " 5. Review governance tags for compliance with organizational standards" -ForegroundColor White
Write-Host " 6. Consider implementing Azure Application Insights for application monitoring" -ForegroundColor White
Write-Host ""
Write-Host "✅ Azure Web Apps inventory completed successfully!" -ForegroundColor Green
Write-Host "======================================================================================================================================================================"
} catch {
Write-Host ""
Write-Host "❌ CRITICAL ERROR OCCURRED" -ForegroundColor Red
Write-Host "======================================================================================================================================================================"
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Line: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red
Write-Host "Position: $($_.InvocationInfo.OffsetInLine)" -ForegroundColor Red
Write-Host ""
Write-Host "🔧 TROUBLESHOOTING STEPS:" -ForegroundColor Yellow
Write-Host " 1. Verify you are authenticated to Azure (Connect-AzAccount)" -ForegroundColor White
Write-Host " 2. Ensure you have Website Contributor or Reader permissions on App Service resources" -ForegroundColor White
Write-Host " 3. Check that the Management Group Reader role is assigned" -ForegroundColor White
Write-Host " 4. Verify Azure PowerShell modules are installed and up to date" -ForegroundColor White
Write-Host " 5. Confirm that deployment API permissions are available for deployment tracking" -ForegroundColor White
Write-Host " 6. Try running the script with -Verbose for additional diagnostic information" -ForegroundColor White
Write-Host ""
Write-Host "📞 For additional support, contact the Cloud Engineering team" -ForegroundColor Cyan
Write-Host "======================================================================================================================================================================"
# Ensure we exit with error code for automation scenarios
exit 1
} finally {
# Reset progress preference
$ProgressPreference = "Continue"
}