PowerShell 7.5.0 Preview 2 Changes

Preview 2 has dropped! Let’s look at some of the major things that are coming in this preview.

First – let’s look at the breaking changes:

Breaking Changes

  • Adjustments have been made to the -OlderThan and -NewerThan parameters for Test-Path, addressing issues when used with PathType and a date range​
  • The default CatalogVersion for New-FileCatalog has been changed to 2​​
  • Restrictions have been added to block help from network locations in restricted remoting sessions​

And now to the improvements:

Tab Completion Improvements

  • Efforts led by contributors like @ArmaanMcleod have refined tab completion, including preventing fallback to file completion when tab completing type names and adding argument completers for various cmdlets like Set-StrictMode, Get-Verb, Start-Process, and more​

Web Cmdlets Improvements

  • Fixes have been made to Invoke-WebRequest to ensure accurate size reporting when the -Resume parameter is specified​
  • Web Cmdlets have been adjusted to allow WinForm applications to work correctly​

Other Cmdlet Improvements

  • A range of cmdlet improvements and fixes, including for Test-Connection, Get-Service, and adding new parameters to New-Guid. For instance, New-Guid now supports -Empty and -InputObject parameters​

Engine Improvements

  • Enhancements in the engine include telemetry for module imports, adjustments to module load telemetry, and fixes for regressions introduced by the WDAC logging feature​

Experimental Features

  • Introduction of tilde expansion for Windows native executables, demonstrating PowerShell’s ongoing evolution and adaptation​

​.

PowerShell, GitHub, and the GH Cli

Here’s another quick one – using the GitHub CLI to copy a repository from GitHub, bring it down to your local machine, and then copy it to a remote server. Great for quickly setting up a new automation server or re-baseline an existing box.

Before running the script, ensure:

  • You have permissions to access the remote server and write to the target directory.
  • PowerShell Remoting is enabled on the remote server if you’re using a PowerShell session for the transfer.
  • The GitHub CLI is installed and configured on your machine.

Here’s the script!

# Define variables
$repoUrl = "https://github.com/username/repository" # Replace with your GitHub repository URL
$tempDir = Join-Path -Path $env:TEMP -ChildPath "github_repo_temp"
$remoteServer = "\\remote-server\share" # Replace with your remote server share path
$remotePath = "path\to\destination\folder" # Replace with your remote destination path, relative to the share

# Ensure the temp directory does not already exist
if (Test-Path -Path $tempDir) {
    Remove-Item -Path $tempDir -Recurse
}

# Clone the repository to the temporary directory
gh repo clone $repoUrl $tempDir

# Assuming the remote server is accessible via a network share
$fullRemotePath = Join-Path -Path $remoteServer -ChildPath $remotePath

# Ensure the remote directory exists
if (-not (Test-Path -Path $fullRemotePath)) {
    New-Item -Path $fullRemotePath -ItemType Directory
}

# Copy the contents to the remote server
Copy-Item -Path "$tempDir\*" -Destination $fullRemotePath -Recurse

# Clean up the temporary directory
Remove-Item -Path $tempDir -Recurse

Write-Host "Repository contents have been copied to the remote server."

And that’s it! Consider putting it in a scheduler to always ensure that your remote automation servers are up to date and using your latest code.

Enjoy!

Running PowerShell from a Logic App

Hola! Today let’s look at a simple way to get PowerShell scripts to run from a Logic App. It will involve a single extra tool, but this really adds versatility to an already versatile tool.

Start by creating a PowerShell script for your specific task. This script will be uploaded to an Azure Automation Runbook. For instance, if you aim to manage VMs, ensure the script includes Azure RM or Az module commands to start, stop, or monitor VM states. Here is an example:

# Sample PowerShell Script to Start a Specific Azure VM
Param(
    [string]$vmName,
    [string]$resourceGroupName
)

Connect-AzAccount -Identity
Start-AzVM -Name $vmName -ResourceGroupName $resourceGroupName

Obviously this is a short script that we can do with just Logic Apps (and not involve pwsh at all), but you get the point.

Now – Upload and publish your PowerShell script in an Azure Automation Runbook.

  1. In your Azure Automation Account, create a new Runbook.
  2. Choose “PowerShell” as the Runbook type.
  3. Import your script and publish the Runbook.

Go ahead test the runbook if you want.

Next – create a Logic App to trigger the Runbook. You might use a schedule, an HTTP request, or another event in Azure as a trigger.

  1. In the Logic App Designer, add a new step and search for the “Azure Automation” connector.
  2. Select “Create job” action.
  3. Fill in the necessary details: Automation Account, Runbook Name, and parameters (if your script requires them). In our example we might dynamically pass the VM name, or maybe look for only VMs that are off and loop through them.

For more complex scenarios, you might need to integrate with other Azure services before or after executing your PowerShell script:

  • Azure Functions: For custom logic that cannot be implemented directly in PowerShell or needs a specific runtime environment.
  • Azure Event Grid: To trigger your Logic App based on events from various Azure services.
  • Azure Monitor: To analyze logs and metrics from your Logic App and Automation Runbooks, enabling proactive management and optimization of your automated tasks.

And there you go! Go put PowerShell everywhere!

Quick Code – Install AMA and Assign a DCR with PowerShell

Happy Holidays! Here’s a quick post to share some code that will inventory Azure VMs, install the AMA if necessary, and then assign a DCR to the VM.

# Ensure you're logged in to Azure
Connect-AzAccount

# Define the Data Collection Rule (DCR) resource ID
$dcrResourceId = "<Your-DCR-Resource-ID>"

# Get all VMs in the subscription
$vms = Get-AzVM

# Use ForEach-Object with -Parallel to process VMs concurrently
$vms | ForEach-Object -Parallel {
    $vm = $_
    $osType = $vm.StorageProfile.OsDisk.OsType
    $extensionName = if ($osType -eq "Windows") { "AzureMonitorWindowsAgent" } else { "AzureMonitorLinuxAgent" }
    $extensionPublisher = "Microsoft.Azure.Monitor"
    $vmResourceId = "/subscriptions/$using:vm.SubscriptionId/resourceGroups/$using:vm.ResourceGroupName/providers/Microsoft.Compute/virtualMachines/$using:vm.Name"

    try {
        # Check if the Azure Monitor Agent extension is installed
        $amaExtension = Get-AzVMExtension -ResourceGroupName $using:vm.ResourceGroupName -VMName $using:vm.Name -Name $extensionName -ErrorAction SilentlyContinue

        if (-not $amaExtension) {
            try {
                # Install the Azure Monitor Agent extension
                Set-AzVMExtension -ResourceGroupName $using:vm.ResourceGroupName -VMName $using:vm.Name -Name $extensionName -Publisher $extensionPublisher -ExtensionType $extensionName -TypeHandlerVersion "1.0" -Location $using:vm.Location
                Write-Host "Installed Azure Monitor Agent on $($using:vm.Name)"
            } catch {
                Write-Host "Failed to install Azure Monitor Agent on $($using:vm.Name): $_"
            }
        } else {
            Write-Host "Azure Monitor Agent is already installed on $($using:vm.Name)"
        }
    } catch {
        Write-Host "Error checking Azure Monitor Agent on $($using:vm.Name): $_"
    }

    try {
        # Assign the DCR to the VM
        $settings = @{ "dataCollectionRuleResourceIds" = @($using:dcrResourceId) }
        Set-AzVMExtension -ResourceGroupName $using:vm.ResourceGroupName -VMName $using:vm.Name -Name "AzureMonitorVmExtension" -Publisher $extensionPublisher -ExtensionType $extensionName -Settings $settings -Location $using:vm.Location
        Write-Host "Assigned DCR to $($using:vm.Name)"
    } catch {
        Write-Host "Failed to assign DCR to $($using:vm.Name): $_"
    }
} -ThrottleLimit 5 # Adjust the ThrottleLimit as necessary

Setting up Azure OpenAI with PowerShell

If haven’t been living under a rock, you know that Azure OpenAI is a powerful tool that brings the cutting-edge capabilities of OpenAI’s models to the cloud, offering scalability, reliability, and integration with Azure’s vast ecosystem.

Because I am who I am we will use PowerShell to setup our Azure OpenAI instance. Whether you’re automating deployment or integrating Azure OpenAI into your existing infrastructure, PowerShell scripts can simplify the process. Let’s get started with a step-by-step guide to setting up your Azure OpenAI instance using PowerShell.

Prerequisites

Before we dive into the commands, ensure you have the following:

  • An Azure subscription. If you don’t have one, you can create a free account.
  • PowerShell installed on your system. If you’re on Windows, you’re probably already set. For Mac and Linux users, check out PowerShell Core.
  • The Azure PowerShell module installed. You can install it by running Install-Module -Name Az -AllowClobber -Scope CurrentUser in your PowerShell terminal.

Step 1: Log in to Azure

First things first, let’s log into Azure. Open your PowerShell terminal and run:

Connect-AzAccount

This command opens a login window where you can enter your Azure credentials. Once authenticated, you’re ready to proceed.

Step 2: Create a Resource Group

Azure OpenAI instances need to reside in a resource group, a container that holds related resources for an Azure solution. To create a new resource group, use:

New-AzResourceGroup -Name 'MyResourceGroup' -Location 'EastUS'

Replace 'MyResourceGroup' with your desired resource group name and 'EastUS' with your preferred location.

Step 3: Register the OpenAI Resource Provider

Before deploying Azure OpenAI, ensure your subscription is registered to use the OpenAI resource provider. Register it with:

powershell

Register-AzResourceProvider -ProviderNamespace 'Microsoft.OpenAI'

This command might take a few minutes. To check the status, you can run Get-AzResourceProvider -ProviderNamespace 'Microsoft.OpenAI'.

Step 4: Create an Azure OpenAI Instance

Now, the exciting part—creating your Azure OpenAI instance. Use the following command:

powershell

New-AzResource -ResourceGroupName 'MyResourceGroup' -ResourceType 'Microsoft.OpenAI/workspaces' -Name 'MyOpenAIInstance' -Location 'EastUS' -PropertyObject @{ sku = 'S0'; properties = @{ description = 'My Azure OpenAI instance for cool AI projects'; } }

Make sure to replace 'MyResourceGroup', 'MyOpenAIInstance', and 'EastUS' with your resource group name, desired OpenAI instance name, and location, respectively.

Step 5: Confirm Your Azure OpenAI Instance

To ensure everything went smoothly, you can list all OpenAI instances in your resource group:

powershell

Get-AzResource -ResourceGroupName 'MyResourceGroup' -ResourceType 'Microsoft.OpenAI/workspaces'

This command returns details about the OpenAI instances in your specified resource group, confirming the successful creation of your instance. Enjoy your brand new OpenAI instance!

Deploy Logic Apps with PowerShell

This post is basically just a way to refresh my memory when in the next 3 months I completely forget how easy this is. Here’s how you can leverage PowerShell to manage your Logic Apps and their connections more effectively.

# Define variables
$resourceGroupName = 'YourResourceGroup'
$logicAppName = 'YourLogicAppName'
$templateFilePath = 'path/to/your/template.json'
$parametersFilePath = 'path/to/your/parameters.json'

# Deploy the Logic App
New-AzResourceGroupDeployment -Name DeployLogicApp `
  -ResourceGroupName $resourceGroupName `
  -TemplateFile $templateFilePath `
  -TemplateParameterFile $parametersFilePath

If you need a template example or parameters example, check the end of this post!!

Managing Logic App Connections with PowerShell

PowerShell can also simplify the creation and management of Logic App connections, making it easier to connect to services like Office 365 or custom APIs:

# Creating a connection to Office 365
$connectionName = 'office365Connection'
$connectionParams = @{
    'token:TenantId' = '<YourTenantId>';
    'token:PrincipalId' = '<YourPrincipalId>';
    'token:ClientSecret' = '<YourClientSecret>'
}

New-AzResource -ResourceType 'Microsoft.Web/connections' -ResourceName $connectionName `
  -ResourceGroupName $resourceGroupName -Location 'eastus' `
  -Properties $connectionParams

Sample Template and Parameter Json Files:

Template:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "type": "Microsoft.Logic/workflows",
      "apiVersion": "2019-05-01",
      "name": "[parameters('logicAppName')]",
      "location": "[parameters('location')]",
      "properties": {
        "state": "Enabled",
        "definition": {
          "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
          "contentVersion": "1.0.0.0",
          "triggers": {
            "When_a_HTTP_request_is_received": {
              "type": "Request",
              "kind": "Http",
              "inputs": {
                "method": "POST",
                "schema": {}
              }
            }
          },
          "actions": {
            "Send_an_email": {
              "type": "ApiConnection",
              "inputs": {
                "host": {
                  "connection": {
                    "name": "@parameters('$connections')['office365']['connectionId']"
                  }
                },
                "method": "post",
                "body": {
                  "Subject": "Email Subject Here",
                  "Body": "<p>Email Body Here</p>",
                  "To": "example@example.com"
                },
                "path": "/Mail"
              }
            }
          },
          "outputs": {}
        },
        "parameters": {
          "$connections": {
            "defaultValue": {},
            "type": "Object"
          }
        }
      }
    }
  ],
  "parameters": {
    "logicAppName": {
      "defaultValue": "YourLogicAppName",
      "type": "String"
    },
    "location": {
      "defaultValue": "eastus",
      "type": "String"
    }
  }
}

Parameters:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "logicAppName": {
      "value": "YourLogicAppName"
    },
    "location": {
      "value": "eastus"
    }
  }
}

Automating Azure Service Health Alerts with PowerShell

Hello, Azure amigos! Today we’re diving into the depths of automating Azure Service Health alerts using PowerShell.

What’s Azure Service Health Anyway?

Azure Service Health provides personalized alerts and guidance when Azure service issues affect you. It breaks down into three main types of alerts:

  • Service issues: Problems in Azure services that affect you right now.
  • Planned maintenance: Upcoming maintenance that can affect your services in the future.
  • Health advisories: Issues that require your attention but don’t directly impact Azure services (e.g., security vulnerabilities, deprecated features).

Now, onto the fun part—automating these alerts with PowerShell!

Prerequisites

I’ll assume you’ve got the Azure PowerShell module installed and you’re familiar with the basics of PowerShell scripting and Azure. If not, it’s like assuming you can cook a gourmet meal without knowing how to turn on the stove—start there first!

Let’s get one more thing worked out – creating an action group to use in the Alert Rule.

$ActionGroupName = "MyActionGroup"
$ResourceGroupName = "MyResourceGroup"
$ShortName = "MyAG"

# Replace these values with your actual email and phone number
$Email = "your-email@domain.com"
$Sms = "+1-555-867-5309"

# Creating the action group
New-AzActionGroup -ResourceGroupName $ResourceGroupName -Name $ActionGroupName -ShortName $ShortName -EmailReceiver $Email -SmsReceiver $Sms -Location "Global"

With our action group ready, it’s time to define what we’re actually alerting on. We can create alerts for specific issues, maintenance events, or advisories. Here’s how:

# Assuming you've already created an action group as per the previous steps

$ResourceGroupName = "MyResourceGroup"
$RuleName = "MyServiceHealthAlert"
$ActionGroupId = (Get-AzActionGroup -ResourceGroupName $ResourceGroupName -Name "MyActionGroup").Id

# Service Health alert criteria
$criteria = New-AzActivityLogAlertCondition -Field 'category' -Equal 'ServiceHealth'

# Creating the Service Health alert
Set-AzActivityLogAlert -Location "Global" -Name $RuleName -ResourceGroupName $ResourceGroupName -Scope "/subscriptions/your-subscription-id" -Condition $criteria -ActionGroup $ActionGroupId

This PowerShell command creates an alert rule specifically for Service Health notifications within Azure. It triggers based on the ‘ServiceHealth’ category in the Azure Activity Log, ensuring you’re notified whenever there are relevant service health events affecting your subscription.

Explanation:

  • $criteria: This line defines what we’re alerting on. In this case, it’s any activity log entries with a category of ‘ServiceHealth’.
  • Set-AzActivityLogAlert: This cmdlet creates or updates an activity log alert rule. We specify the alert name, the scope (usually your subscription or a resource group), the conditions under which to trigger, and the action group to notify.

And there ya go! Simple and quick. Enjoy your new Alert Rule!

Optimizing Azure Cost Management with PowerShell

Let’s dig into some quick hits for trying to keep your costs down in Azure – and since I am who I am let’s use PowerShell

Automating Cost Reports

First – lets script the retrieval of usage and cost data, businesses can monitor their cloud expenditures closely, identify trends, and make informed decisions to optimize costs.

Get-AzConsumptionUsageDetail -StartDate "2023-01-01" -EndDate "2023-01-31" | Export-Csv -Path "./AzureCostsJan.csv"

This simple script fetches the consumption details for January 2023 and exports the data to a CSV file – from there you can use something like Excel to dig into your big costs.

Identifying Underutilized Resources

PowerShell scripts can scan Azure services to pinpoint underutilized resources, such as VMs with low CPU utilization or oversized and underused storage accounts, which are prime candidates for downsizing or deletion to cut costs.

Get-AzVM | ForEach-Object {
    $metrics = Get-AzMetric -ResourceId $_.Id -MetricName "Percentage CPU" -TimeGrain "00:05:00" -StartTime (Get-Date).AddDays(-30) -EndTime (Get-Date)
    $avgCpu = ($metrics.Data | Measure-Object -Property Average -Average).Average
    if ($avgCpu -lt 10) {
        Write-Output "$($_.Name) is underutilized."
    }
}

This script assesses VMs for low CPU usage, identifying those with an average CPU utilization below 10% over the last 30 days.

Implementing Budget Alerts

Setting up budget alerts with PowerShell helps prevent unexpected overspending by notifying you when your costs approach predefined thresholds.

$budget = New-AzConsumptionBudget -Amount 1000 -Category Cost -TimeGrain Monthly -StartDate 2023-01-01 -EndDate 2023-12-31 -Name "MonthlyBudget" -NotificationKey "90PercentAlert" -NotificationThreshold 90 -ContactEmails "admin@example.com"

This script creates a monthly budget of $1000 and sets up an alert to notify specified contacts via email when 90% of the budget is consumed.

And there you go! Some quick and easy scripts to make sure you don’t blow your Azure budget!

Hidden Gems in PowerShell 7

PowerShell 7 introduces several lesser-known features that can significantly enhance your scripting prowess. Let’s dive into these hidden gems.

Ternary Operator for Concise Conditional Logic

PowerShell 7 brings the ternary operator (?:), a shorthand for simple if-else statements, allowing for more concise and readable code.

$result = ($value -eq 10) ? "Equal to 10" : "Not equal to 10"

Pipeline Parallelization with ForEach-Object -Parallel

The -Parallel parameter in ForEach-Object can dramatically improve performance by executing script blocks in parallel. Note that it requires the use of the -ThrottleLimit parameter to control the number of concurrent threads.

1..50 | ForEach-Object -Parallel { $_ * 2 } -ThrottleLimit 10

Simplified Error Viewing with $ErrorView and Get-Error

PowerShell 7 introduces a new view for error messages through the $ErrorView variable, which can be set to ConciseView for a more streamlined error display. Additionally, Get-Error provides detailed error information, perfect for troubleshooting.

$ErrorView = 'ConciseView'
Get-Error

Null Conditional Operators for Handling $null

The null conditional operators ?. and ?[] provide a safe way to access properties and methods or index into arrays when there’s a possibility of $null values, preventing unnecessary errors.

$obj = $null
$name = $obj?.Name  # Returns $null without throwing an error
$value = $array?[0] # Safely attempts to access the first element

The switch Statement Enhancements

PowerShell 7 enhances the switch statement with the -Regex and -File options, allowing pattern matching against regex expressions and simplifying file content parsing.

switch -Regex ($inputString) {
    'error' { Write-Output 'Error found' }
    'warning' { Write-Output 'Warning found' }
}

Coalescing Operators for Default Values

The null coalescing operators ?? and ??= simplify the process of providing default values for potentially $null variables, reducing the need for verbose if statements.

$name = $null
$displayName = $name ?? 'Default Name'

Automatic Unwrapping of Single-Element Arrays

A subtle but handy feature; when a command or expression returns an array with a single element, PowerShell 7 automatically unwraps it, eliminating the need for manual indexing to access the single item.

Enhanced JSON Handling with ConvertFrom-Json and ConvertTo-Json

Improvements to ConvertFrom-Json and ConvertTo-Json cmdlets include better depth handling and the ability to work with PSCustomObject instances, streamlining JSON serialization and deserialization.

$json = '{"name": "PowerShell", "version": 7}'
$obj = $json | ConvertFrom-Json

Invoke DSC Resources Directly from PowerShell 7

Directly invoking Desired State Configuration (DSC) resources within PowerShell 7 scripts bridges traditional configuration management with modern PowerShell scripting, enhancing automation capabilities.

There ya go! Hope you find something in here that makes coding a bit more fun/easy!

Cranking Up the Efficiency: Optimizing PowerShell Scripts

Hey there, PowerShell aficionados! Whether you’re automating your morning coffee or deploying a fleet of VMs into the cloud, efficiency is key. Nobody wants to watch paint dry while their script runs in the background. So, let’s put some pep into that PowerShell script of yours. We’re diving straight into the realm of optimization – no fluff, just the good stuff.

Measure, Then Cut: Profiling Your Script

Before you start tweaking, let’s figure out where the bottlenecks are. PowerShell, being the Swiss Army knife it is, comes equipped with some nifty profiling tools like Measure-Command. This cmdlet lets you time how long it takes for a script or command to run. Use it to identify slow parts of your script:

Measure-Command { .\YourScript.ps1 }

Lean and Mean: Streamlining Execution

1. Filter Left, Format Right

One of the golden rules for optimizing PowerShell scripts is to do your filtering as early as possible. Use cmdlets like Where-Object and Select-Object judiciously to trim down your data before processing it further. Remember, processing less data means faster execution:

Get-Process | Where-Object { $_.CPU -gt 100 } | Select-Object Name, CPU

2. Avoid the Pipeline When Possible

While the pipeline is one of PowerShell’s most powerful features, it’s not always the most efficient. Each pipe operation adds overhead. For tight loops or operations that need to be as fast as possible, consider using .NET collections or array manipulations:

$processes = Get-Process
$highCpuProcesses = [System.Collections.ArrayList]@()
foreach ($process in $processes) {
    if ($process.CPU -gt 100) {
        [void]$highCpuProcesses.Add($process)
    }
}

3. Use Foreach-Object Carefully

Foreach-Object is versatile but can be slower than its foreach loop counterpart due to pipeline overhead. For large datasets, stick to foreach for better performance:

# Slower
Get-Process | Foreach-Object { $_.Kill() }

# Faster
foreach ($process in Get-Process) {
    $process.Kill()
}

The Need for Speed: Parallel Processing

When you’re dealing with tasks that can be run concurrently, PowerShell 7’s ForEach-Object -Parallel can be a game-changer. This allows you to run multiple operations at the same time, significantly speeding up processes:

1..10 | ForEach-Object -Parallel { Start-Sleep -Seconds $_; "Slept for $_ seconds" } -ThrottleLimit 10

A Parting Tip: Stay Up-to-Date

PowerShell and .NET are constantly evolving, with new features and performance improvements being added regularly. Make sure your PowerShell version is up-to-date to take advantage of these enhancements.

Wrap-Up

Optimizing PowerShell scripts can turn a sluggish sequence of commands into a streamlined process that runs at lightning speed. By measuring performance, refining your approach, and employing parallel processing, you can ensure your scripts are not only efficient but also maintainable. Happy scripting, and may your execution times always be minimal!