sentenz / convention

General articles, conventions, and guides.
https://sentenz.github.io/convention/
Apache License 2.0
4 stars 2 forks source link

Create an article about `PowerShell` #347

Closed sentenz closed 4 months ago

sentenz commented 4 months ago

Create Modules

Certainly! Here are the example files and usage instructions based on the previous answer.

Step-by-Step Example

  1. Create the Module Directory

Create the module directory. You can do this using PowerShell:

New-Item -Path "$env:USERPROFILE\Documents\PowerShell\Modules\MyModule" -ItemType Directory
  1. Create the Module File

Create the .psm1 file within the module directory:

New-Item -Path "$env:USERPROFILE\Documents\PowerShell\Modules\MyModule\MyModule.psm1" -ItemType File
  1. Add Functions to the Module File

Edit the MyModule.psm1 file and add the following content:

# MyModule.psm1

<#
.SYNOPSIS
    Imports environment variables from a specified file.
.DESCRIPTION
    Reads a file containing environment variables in the format `NAME=VALUE` and sets them in the current environment.
.PARAMETER FilePath
    The path to the file containing environment variables.
.EXAMPLE
    Import-EnvFile -FilePath "C:\path\to\.env"
#>
function Import-EnvFile {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilePath
    )

    if (-not (Test-Path -Path $FilePath -PathType Leaf)) {
        Write-Error "Environment file '$FilePath' not found."
        return
    }

    try {
        $envVars = Get-Content -Path $FilePath | ForEach-Object {
            $name, $value = $_ -split "=", 2
            [System.Collections.DictionaryEntry]::new($name.Trim(), $value.Trim())
        }

        $envVars | ForEach-Object {
            [System.Environment]::SetEnvironmentVariable($_.Key, $_.Value)
            Write-Verbose "Set environment variable '$($_.Key)' to '$($_.Value)'"
        }
    } catch {
        Write-Error "An error occurred while importing environment variables: $_"
    }
}

<#
.SYNOPSIS
    Retrieves the value of a specified environment variable.
.DESCRIPTION
    Gets the value of an environment variable. If the variable is not set, an error is thrown.
.PARAMETER VariableName
    The name of the environment variable to retrieve.
.EXAMPLE
    $value = Get-EnvVariable -VariableName "PATH"
#>
function Get-EnvVariable {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$VariableName
    )

    try {
        $value = [System.Environment]::GetEnvironmentVariable($VariableName)
        if (-not $value) {
            Write-Error "Environment variable '$VariableName' is not set."
            return
        }

        return $value
    } catch {
        Write-Error "An error occurred while retrieving the environment variable: $_"
    }
}
  1. Create a Module Manifest (Optional)

Create the module manifest file:

New-ModuleManifest -Path "$env:USERPROFILE\Documents\PowerShell\Modules\MyModule\MyModule.psd1" -RootModule "MyModule.psm1"
  1. Import the Module

In your PowerShell session, import the module:

Import-Module MyModule
  1. Usage Examples

Example 1: Using Import-EnvFile Function

  1. Create an environment file (e.g., C:\path\to\.env) with the following content:

    VARIABLE1=VALUE1
    VARIABLE2=VALUE2
  2. Use the Import-EnvFile function to import the environment variables:

    Import-EnvFile -FilePath "C:\path\to\.env"

Example 2: Using Get-EnvVariable Function

  1. Retrieve an environment variable (e.g., PATH):

    $pathValue = Get-EnvVariable -VariableName "PATH"
    Write-Output "PATH value: $pathValue"

Summary of Files Created

  1. Directory Structure

    C:\Users\<YourUsername>\Documents\PowerShell\Modules\MyModule\
    ├── MyModule.psm1
    └── MyModule.psd1 (optional)
  2. Module File (MyModule.psm1)

    # MyModule.psm1
    (Content as provided above)
  3. Environment File (.env)

    VARIABLE1=VALUE1
    VARIABLE2=VALUE2
  4. PowerShell Script to Import Module and Use Functions

    # Import the module
    Import-Module MyModule
    
    # Import environment variables from a file
    Import-EnvFile -FilePath "C:\path\to\.env"
    
    # Retrieve the value of an environment variable
    $pathValue = Get-EnvVariable -VariableName "PATH"
    Write-Output "PATH value: $pathValue"

These steps provide a complete example of how to create, structure, and use PowerShell modules for better function management and reusability.

sentenz commented 4 months ago

1 Init Module

Sure, here is a PowerShell script to initialize custom modules located in the scripts/powershell directory. This script assumes that your custom modules are stored as .psm1 files in that directory.

# Define the path to the custom modules
$customModulesPath = "$PSScriptRoot\scripts\powershell"

# Check if the directory exists
if (Test-Path -Path $customModulesPath) {
    # Get all the module files in the directory
    $moduleFiles = Get-ChildItem -Path $customModulesPath -Filter *.psm1

    # Import each module
    foreach ($moduleFile in $moduleFiles) {
        try {
            Import-Module -Name $moduleFile.FullName -ErrorAction Stop
            Write-Host "Successfully imported module: $($moduleFile.Name)" -ForegroundColor Green
        } catch {
            Write-Host "Failed to import module: $($moduleFile.Name)" -ForegroundColor Red
            Write-Host $_.Exception.Message -ForegroundColor Red
        }
    }
} else {
    Write-Host "The directory $customModulesPath does not exist." -ForegroundColor Red
}

Explanation:

  1. Define the path to the custom modules: The $customModulesPath variable is set to the path where your custom modules are located. $PSScriptRoot is used to ensure the path is relative to the location of the script.

  2. Check if the directory exists: The script checks if the directory specified by $customModulesPath exists using Test-Path.

  3. Get all the module files: Get-ChildItem is used to retrieve all files with a .psm1 extension in the specified directory.

  4. Import each module: The script iterates over each .psm1 file and attempts to import it using Import-Module. Success and error messages are displayed accordingly.

  5. Error handling: The script uses a try and catch block to handle any errors that might occur during the import process, providing detailed error messages to help diagnose issues.

Usage:

  1. Save the script to a .ps1 file, for example, InitializeModules.ps1.
  2. Place this script in a directory of your choice.
  3. Run the script using PowerShell.

This script helps ensure that all custom modules in the specified directory are loaded and available for use in your PowerShell session, making it easier to manage and utilize custom functionality across your scripts.

2. Task

To execute the InitializeModules.ps1 script within a Makefile target, you can use the powershell command. Below is an example of how you can structure your Makefile to include a target that runs the PowerShell script.

# Makefile

# Define the target to initialize PowerShell modules
.PHONY: init-modules

init-modules:
    powershell -ExecutionPolicy Bypass -File ./InitializeModules.ps1

Explanation:

  1. Define the target: The init-modules target is defined as a phony target using .PHONY: init-modules. This indicates that init-modules is not a file name but a label for a command sequence.

  2. Run the PowerShell script: The powershell command is used to execute the InitializeModules.ps1 script.

    • -ExecutionPolicy Bypass allows the script to run without being blocked by the execution policy.
    • -File specifies the path to the PowerShell script.

Steps to Execute:

  1. Ensure the PowerShell script is created and saved as InitializeModules.ps1 in the appropriate directory.

  2. Create a Makefile in the same directory or adjust the path accordingly if they are in different locations.

  3. Run the Makefile target from the command line using the make command:

    make init-modules

This setup allows you to run the PowerShell initialization script from a Makefile, providing a streamlined way to manage dependencies and initialization tasks in your development workflow.

3. Handling Scripts

The provided script is already robust, but there are a few additional optimizations and enhancements that can be considered to further align with best practices and principles in scripting:

Optimizations and Enhancements

  1. Parameterization: Make the script more flexible by parameterizing paths and other variables. This allows for easier reuse and modification.

  2. Verbose Logging: Include verbose logging to help with debugging and understanding script execution flow.

  3. Error Variable: Utilize the $Error automatic variable for more detailed error handling and logging.

  4. Strict Mode: Enable strict mode to catch common scripting errors, such as using uninitialized variables.

Enhanced Script with Optimizations

param (
    [string]$TargetDirectory = "C:\NewDirectory",
    [string]$LogFilePath = "C:\Log.txt",
    [string]$ErrorLogFilePath = "C:\ErrorLog.txt"
)

# Enable strict mode
Set-StrictMode -Version Latest

& {
    Push-Location -Path $TargetDirectory
    try {
        # Verbose logging
        Write-Verbose "Changing to directory: $TargetDirectory"

        # Example commands
        Get-ChildItem

        # More commands
        $successMessage = "Listing of $TargetDirectory completed successfully."
        Write-Output $successMessage | Out-File -FilePath $LogFilePath -Append
        Write-Verbose $successMessage
    }
    catch {
        # Handle the error
        $errorMessage = "An error occurred: $_"
        Write-Error $errorMessage

        # Log the error details
        $detailedError = $Error[0] | Out-String
        $detailedError | Out-File -FilePath $ErrorLogFilePath -Append

        Write-Verbose $detailedError
    }
    finally {
        # Ensure the original directory is restored
        Pop-Location

        # Verbose logging
        Write-Verbose "Returned to the original directory."
    }
}

Explanation of Optimizations

  1. Parameterization:

    • The script accepts parameters for the target directory, log file path, and error log file path. This makes the script flexible and reusable.
  2. Verbose Logging:

    • Use Write-Verbose to provide detailed logging for script actions. This is useful for debugging and understanding the script's execution flow.
  3. Error Variable:

    • The $Error automatic variable is used to capture detailed error information. This provides more context in the error log.
  4. Strict Mode:

    • Set-StrictMode -Version Latest ensures that common scripting errors, such as using uninitialized variables, are caught early, promoting better script quality.

Usage Example

When running the script, you can specify the parameters as needed:

.\YourScript.ps1 -TargetDirectory "C:\AnotherDirectory" -LogFilePath "C:\AnotherLog.txt" -ErrorLogFilePath "C:\AnotherErrorLog.txt"

By incorporating these optimizations, the script becomes more flexible, easier to maintain, and provides better logging and error handling, making it more robust and aligned with best practices for PowerShell scripting.

4. Main Script

To avoid duplicate code and make the script modular, you can create a separate functions script (EnvFunctions.ps1) that contains the logic for reading and setting environment variables from a .env file. Then, you can import this script in setup.ps1 and use the functions.

Step 1: Create EnvFunctions.ps1

function Import-EnvFile {
    param (
        [string]$FilePath
    )

    if (Test-Path $FilePath) {
        $envVars = Get-Content $FilePath | ForEach-Object {
            $name, $value = $_ -split "="
            [System.Collections.DictionaryEntry]::new($name.Trim(), $value.Trim())
        }

        $envVars | ForEach-Object {
            [System.Environment]::SetEnvironmentVariable($_.Key, $_.Value)
        }
    } else {
        Write-Error "Environment file '$FilePath' not found."
        exit 1
    }
}

function Get-EnvVariable {
    param (
        [string]$VariableName
    )

    $value = [System.Environment]::GetEnvironmentVariable($VariableName)
    if (-not $value) {
        Write-Error "Environment variable '$VariableName' is not set."
        exit 1
    }

    return $value
}

Step 2: Update setup.ps1 to Use EnvFunctions.ps1

# Import the functions script
. .\EnvFunctions.ps1

# Load the .env file and set variables
$envFilePath = ".\.env"
Import-EnvFile -FilePath $envFilePath

# Retrieve environment variables
$TargetDirectory = Get-EnvVariable -VariableName "TargetDirectory"
$LogFilePath = Get-EnvVariable -VariableName "LogFilePath"
$ErrorLogFilePath = Get-EnvVariable -VariableName "ErrorLogFilePath"

# Call YourScript.ps1 within a script block
& {
    .\YourScript.ps1 -TargetDirectory $TargetDirectory -LogFilePath $LogFilePath -ErrorLogFilePath $ErrorLogFilePath
}

Explanation

  1. EnvFunctions.ps1:

    • Import-EnvFile: Reads the .env file and sets environment variables.
    • Get-EnvVariable: Retrieves an environment variable and checks if it is set.
  2. setup.ps1:

    • Imports the functions from EnvFunctions.ps1.
    • Calls Import-EnvFile to read the .env file and set environment variables.
    • Calls Get-EnvVariable to retrieve and validate the required environment variables.
    • Calls YourScript.ps1 within a script block, passing the retrieved variables as parameters.

Usage

  1. Ensure EnvFunctions.ps1, YourScript.ps1, and setup.ps1 are in the same directory.
  2. Run the setup.ps1 script in PowerShell:
.\setup.ps1

This approach ensures that the logic for reading and setting environment variables is encapsulated in reusable functions, avoiding code duplication and promoting a modular script structure.

5. Make

To integrate the setup.ps1 script into a make target, you will need to create a Makefile. make is commonly used in Unix-like environments, but you can also use it in Windows if you have a compatible version of make installed (e.g., via Cygwin, Git Bash, or by installing GNU Make for Windows).

Step 1: Create the Makefile

Here's an example Makefile to run the setup.ps1 script:

.PHONY: setup

# Define the Powershell executable path
POWERSHELL = powershell.exe

# Define the script paths
SETUP_SCRIPT = .\setup.ps1

# Make target to run the setup script
setup:
    $(POWERSHELL) -ExecutionPolicy Bypass -File $(SETUP_SCRIPT)

Explanation

Step 2: Usage

  1. Ensure Makefile, EnvFunctions.ps1, YourScript.ps1, and setup.ps1 are in the same directory.
  2. Open a terminal (e.g., Command Prompt, PowerShell, Git Bash).
  3. Navigate to the directory containing the scripts and Makefile.
  4. Run the make setup command:
make setup

Example Directory Structure

/your-directory
    |-- Makefile
    |-- .env
    |-- EnvFunctions.ps1
    |-- YourScript.ps1
    |-- setup.ps1

Notes

This setup allows you to use make to manage the execution of your PowerShell script, fitting into a more traditional build and automation workflow.

sentenz commented 4 months ago

1 Init Module

Sure, here is a PowerShell script to initialize custom modules located in the scripts/powershell directory. This script assumes that your custom modules are stored as .psm1 files in that directory.

# Define the path to the custom modules
$customModulesPath = "$PSScriptRoot\scripts\powershell"

# Check if the directory exists
if (Test-Path -Path $customModulesPath) {
    # Get all the module files in the directory
    $moduleFiles = Get-ChildItem -Path $customModulesPath -Filter *.psm1

    # Import each module
    foreach ($moduleFile in $moduleFiles) {
        try {
            Import-Module -Name $moduleFile.FullName -ErrorAction Stop
            Write-Host "Successfully imported module: $($moduleFile.Name)" -ForegroundColor Green
        } catch {
            Write-Host "Failed to import module: $($moduleFile.Name)" -ForegroundColor Red
            Write-Host $_.Exception.Message -ForegroundColor Red
        }
    }
} else {
    Write-Host "The directory $customModulesPath does not exist." -ForegroundColor Red
}

Explanation:

  1. Define the path to the custom modules: The $customModulesPath variable is set to the path where your custom modules are located. $PSScriptRoot is used to ensure the path is relative to the location of the script.

  2. Check if the directory exists: The script checks if the directory specified by $customModulesPath exists using Test-Path.

  3. Get all the module files: Get-ChildItem is used to retrieve all files with a .psm1 extension in the specified directory.

  4. Import each module: The script iterates over each .psm1 file and attempts to import it using Import-Module. Success and error messages are displayed accordingly.

  5. Error handling: The script uses a try and catch block to handle any errors that might occur during the import process, providing detailed error messages to help diagnose issues.

Usage:

  1. Save the script to a .ps1 file, for example, InitializeModules.ps1.
  2. Place this script in a directory of your choice.
  3. Run the script using PowerShell.

This script helps ensure that all custom modules in the specified directory are loaded and available for use in your PowerShell session, making it easier to manage and utilize custom functionality across your scripts.

2. Task

To execute the InitializeModules.ps1 script within a Makefile target, you can use the powershell command. Below is an example of how you can structure your Makefile to include a target that runs the PowerShell script.

# Makefile

# Define the target to initialize PowerShell modules
.PHONY: init-modules

init-modules:
    powershell -ExecutionPolicy Bypass -File ./InitializeModules.ps1

Explanation:

  1. Define the target: The init-modules target is defined as a phony target using .PHONY: init-modules. This indicates that init-modules is not a file name but a label for a command sequence.

  2. Run the PowerShell script: The powershell command is used to execute the InitializeModules.ps1 script.

    • -ExecutionPolicy Bypass allows the script to run without being blocked by the execution policy.
    • -File specifies the path to the PowerShell script.

Steps to Execute:

  1. Ensure the PowerShell script is created and saved as InitializeModules.ps1 in the appropriate directory.

  2. Create a Makefile in the same directory or adjust the path accordingly if they are in different locations.

  3. Run the Makefile target from the command line using the make command:

    make init-modules

This setup allows you to run the PowerShell initialization script from a Makefile, providing a streamlined way to manage dependencies and initialization tasks in your development workflow.

3. Handling Scripts

The provided script is already robust, but there are a few additional optimizations and enhancements that can be considered to further align with best practices and principles in scripting:

Optimizations and Enhancements

  1. Parameterization: Make the script more flexible by parameterizing paths and other variables. This allows for easier reuse and modification.

  2. Verbose Logging: Include verbose logging to help with debugging and understanding script execution flow.

  3. Error Variable: Utilize the $Error automatic variable for more detailed error handling and logging.

  4. Strict Mode: Enable strict mode to catch common scripting errors, such as using uninitialized variables.

Enhanced Script with Optimizations

param (
    [string]$TargetDirectory = "C:\NewDirectory",
    [string]$LogFilePath = "C:\Log.txt",
    [string]$ErrorLogFilePath = "C:\ErrorLog.txt"
)

# Enable strict mode
Set-StrictMode -Version Latest

& {
    Push-Location -Path $TargetDirectory
    try {
        # Verbose logging
        Write-Verbose "Changing to directory: $TargetDirectory"

        # Example commands
        Get-ChildItem

        # More commands
        $successMessage = "Listing of $TargetDirectory completed successfully."
        Write-Output $successMessage | Out-File -FilePath $LogFilePath -Append
        Write-Verbose $successMessage
    }
    catch {
        # Handle the error
        $errorMessage = "An error occurred: $_"
        Write-Error $errorMessage

        # Log the error details
        $detailedError = $Error[0] | Out-String
        $detailedError | Out-File -FilePath $ErrorLogFilePath -Append

        Write-Verbose $detailedError
    }
    finally {
        # Ensure the original directory is restored
        Pop-Location

        # Verbose logging
        Write-Verbose "Returned to the original directory."
    }
}

Explanation of Optimizations

  1. Parameterization:

    • The script accepts parameters for the target directory, log file path, and error log file path. This makes the script flexible and reusable.
  2. Verbose Logging:

    • Use Write-Verbose to provide detailed logging for script actions. This is useful for debugging and understanding the script's execution flow.
  3. Error Variable:

    • The $Error automatic variable is used to capture detailed error information. This provides more context in the error log.
  4. Strict Mode:

    • Set-StrictMode -Version Latest ensures that common scripting errors, such as using uninitialized variables, are caught early, promoting better script quality.

Usage Example

When running the script, you can specify the parameters as needed:

.\YourScript.ps1 -TargetDirectory "C:\AnotherDirectory" -LogFilePath "C:\AnotherLog.txt" -ErrorLogFilePath "C:\AnotherErrorLog.txt"

By incorporating these optimizations, the script becomes more flexible, easier to maintain, and provides better logging and error handling, making it more robust and aligned with best practices for PowerShell scripting.

4. Main Script

To avoid duplicate code and make the script modular, you can create a separate functions script (EnvFunctions.ps1) that contains the logic for reading and setting environment variables from a .env file. Then, you can import this script in setup.ps1 and use the functions.

Step 1: Create EnvFunctions.ps1

function Import-EnvFile {
    param (
        [string]$FilePath
    )

    if (Test-Path $FilePath) {
        $envVars = Get-Content $FilePath | ForEach-Object {
            $name, $value = $_ -split "="
            [System.Collections.DictionaryEntry]::new($name.Trim(), $value.Trim())
        }

        $envVars | ForEach-Object {
            [System.Environment]::SetEnvironmentVariable($_.Key, $_.Value)
        }
    } else {
        Write-Error "Environment file '$FilePath' not found."
        exit 1
    }
}

function Get-EnvVariable {
    param (
        [string]$VariableName
    )

    $value = [System.Environment]::GetEnvironmentVariable($VariableName)
    if (-not $value) {
        Write-Error "Environment variable '$VariableName' is not set."
        exit 1
    }

    return $value
}

Step 2: Update setup.ps1 to Use EnvFunctions.ps1

# Import the functions script
. .\EnvFunctions.ps1

# Load the .env file and set variables
$envFilePath = ".\.env"
Import-EnvFile -FilePath $envFilePath

# Retrieve environment variables
$TargetDirectory = Get-EnvVariable -VariableName "TargetDirectory"
$LogFilePath = Get-EnvVariable -VariableName "LogFilePath"
$ErrorLogFilePath = Get-EnvVariable -VariableName "ErrorLogFilePath"

# Call YourScript.ps1 within a script block
& {
    .\YourScript.ps1 -TargetDirectory $TargetDirectory -LogFilePath $LogFilePath -ErrorLogFilePath $ErrorLogFilePath
}

Explanation

  1. EnvFunctions.ps1:

    • Import-EnvFile: Reads the .env file and sets environment variables.
    • Get-EnvVariable: Retrieves an environment variable and checks if it is set.
  2. setup.ps1:

    • Imports the functions from EnvFunctions.ps1.
    • Calls Import-EnvFile to read the .env file and set environment variables.
    • Calls Get-EnvVariable to retrieve and validate the required environment variables.
    • Calls YourScript.ps1 within a script block, passing the retrieved variables as parameters.

Usage

  1. Ensure EnvFunctions.ps1, YourScript.ps1, and setup.ps1 are in the same directory.
  2. Run the setup.ps1 script in PowerShell:
.\setup.ps1

This approach ensures that the logic for reading and setting environment variables is encapsulated in reusable functions, avoiding code duplication and promoting a modular script structure.

5. Make

To integrate the setup.ps1 script into a make target, you will need to create a Makefile. make is commonly used in Unix-like environments, but you can also use it in Windows if you have a compatible version of make installed (e.g., via Cygwin, Git Bash, or by installing GNU Make for Windows).

Step 1: Create the Makefile

Here's an example Makefile to run the setup.ps1 script:

.PHONY: setup

# Define the Powershell executable path
POWERSHELL = powershell.exe

# Define the script paths
SETUP_SCRIPT = .\setup.ps1

# Make target to run the setup script
setup:
    $(POWERSHELL) -ExecutionPolicy Bypass -File $(SETUP_SCRIPT)

Explanation

Step 2: Usage

  1. Ensure Makefile, EnvFunctions.ps1, YourScript.ps1, and setup.ps1 are in the same directory.
  2. Open a terminal (e.g., Command Prompt, PowerShell, Git Bash).
  3. Navigate to the directory containing the scripts and Makefile.
  4. Run the make setup command:
make setup

Example Directory Structure

/your-directory
    |-- Makefile
    |-- .env
    |-- EnvFunctions.ps1
    |-- YourScript.ps1
    |-- setup.ps1

Notes

This setup allows you to use make to manage the execution of your PowerShell script, fitting into a more traditional build and automation workflow.

Invoke-ResilientWebRequest

<# .SYNOPSIS A script that makes a resilient web request. .DESCRIPTION This script uses Invoke-WebRequest to make an HTTP request with retry logic to handle transient failures. .PARAMETER Uri The URI to which the request is made. .PARAMETER MaxRetries The maximum number of retry attempts (default is 3). .PARAMETER RetryDelay The delay between retries in seconds (default is 2 seconds). .EXAMPLE .\ResilientInvokeWebRequest.ps1 -Uri "https://example.com" -MaxRetries 5 -RetryDelay 3 .NOTES Additional notes about the script.

>

param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Uri,

[Parameter(Mandatory=$false)]
[int]$MaxRetries = 3,

[Parameter(Mandatory=$false)]
[int]$RetryDelay = 2

)

function Invoke-ResilientWebRequest { param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Uri,

    [Parameter(Mandatory=$false)]
    [int]$MaxRetries = 3,

    [Parameter(Mandatory=$false)]
    [int]$RetryDelay = 2
)

$attempt = 0
$success = $false

while (-not $success -and $attempt -lt $MaxRetries) {
    try {
        $attempt++
        Write-Verbose "Attempt $attempt of $MaxRetries: Sending request to $Uri"
        $response = Invoke-WebRequest -Uri $Uri -ErrorAction Stop
        Write-Output "Request succeeded on attempt $attempt"
        $success = $true
        return $response
    }
    catch {
        Write-Warning "Attempt $attempt failed: $_"
        if ($attempt -lt $MaxRetries) {
            Write-Verbose "Waiting for $RetryDelay seconds before retrying..."
            Start-Sleep -Seconds $RetryDelay
        }
        else {
            Write-Error "All attempts failed. Exiting."
        }
    }
}

}

Call the resilient web request function

Invoke-ResilientWebRequest -Uri $Uri -MaxRetries $MaxRetries -RetryDelay $RetryDelay