EnhancedModuleStarterAO.psm1
#Region '.\Public\Add-EnvPath.ps1' -1 function Add-EnvPath { <# .SYNOPSIS Adds a specified path to the environment PATH variable. .DESCRIPTION The Add-EnvPath function adds a specified path to the environment PATH variable. The path can be added to the session, user, or machine scope. .PARAMETER Path The path to be added to the environment PATH variable. .PARAMETER Container Specifies the scope of the environment variable. Valid values are 'Machine', 'User', or 'Session'. .EXAMPLE Add-EnvPath -Path 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86' -Container 'Machine' #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Path, [ValidateSet('Machine', 'User', 'Session')] [string] $Container = 'Session' ) begin { Write-EnhancedLog -Message "Starting Add-EnvPath function" -Level "INFO" Log-Params -Params @{ Path = $Path Container = $Container } $envPathHashtable = [ordered]@{} $persistedPathsHashtable = [ordered]@{} $containerMapping = @{ Machine = [System.EnvironmentVariableTarget]::Machine User = [System.EnvironmentVariableTarget]::User } } process { try { # Update the PATH variable for User or Machine scope if ($Container -ne 'Session') { $containerType = $containerMapping[$Container] $existingPaths = [System.Environment]::GetEnvironmentVariable('Path', $containerType) -split ';' foreach ($pathItem in $existingPaths) { $persistedPathsHashtable[$pathItem] = $null } if (-not $persistedPathsHashtable.Contains($Path)) { Write-EnhancedLog -Message "Path not found in persisted paths, adding it." -Level "INFO" $persistedPathsHashtable[$Path] = $null [System.Environment]::SetEnvironmentVariable('Path', ($persistedPathsHashtable.Keys -join ';'), $containerType) } } # Update the PATH variable for the current session $existingSessionPaths = $env:Path -split ';' foreach ($sessionPathItem in $existingSessionPaths) { $envPathHashtable[$sessionPathItem] = $null } if (-not $envPathHashtable.Contains($Path)) { Write-EnhancedLog -Message "Path not found in session paths, adding it." -Level "INFO" $envPathHashtable[$Path] = $null $env:Path = $envPathHashtable.Keys -join ';' } } catch { Write-EnhancedLog -Message "An error occurred: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ } } end { Write-EnhancedLog -Message "Exiting Add-EnvPath function" -Level "INFO" Write-Host "The permanent environment PATH variable is:" [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) -split ';' Write-Host "The temporary environment PATH variable is:" $env:Path -split ';' } } # # Example usage # $envPathParams = @{ # Path = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Imaging and Configuration Designer\x86' # Container = 'Machine' # } # Add-EnvPath @envPathParams #EndRegion '.\Public\Add-EnvPath.ps1' 97 #Region '.\Public\Add-Step.ps1' -1 function Add-Step { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Description, [Parameter(Mandatory = $true)] [ScriptBlock]$Action ) Begin { Write-EnhancedLog -Message "Starting Add-Step function" -Level "INFO" Log-Params -Params @{ Description = $Description Action = $Action.ToString() } $global:steps = [System.Collections.Generic.List[PSCustomObject]]::new() } Process { try { Write-EnhancedLog -Message "Adding step: $Description" -Level "INFO" $global:steps.Add([PSCustomObject]@{ Description = $Description; Action = $Action }) } catch { Write-EnhancedLog -Message "An error occurred while adding step: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ } } End { Write-EnhancedLog -Message "Exiting Add-Step function" -Level "INFO" } } # Example usage # Add-Step -Description "Sample step description" -Action { Write-Output "Sample action" } #EndRegion '.\Public\Add-Step.ps1' 39 #Region '.\Public\Authenticate-GitHubCLI.ps1' -1 function Authenticate-GitHubCLI { <# .SYNOPSIS Authenticates with GitHub CLI using a token provided by the user or from a secrets file. .DESCRIPTION This function allows the user to authenticate with GitHub CLI by either entering a GitHub token manually or using a token from a secrets file located in the `$PSScriptRoot`. .PARAMETER GhPath The path to the GitHub CLI executable (gh.exe). .EXAMPLE Authenticate-GitHubCLI -GhPath "C:\Program Files\GitHub CLI\gh.exe" Prompts the user to choose between entering the GitHub token manually or using the token from the secrets file. .NOTES This function requires GitHub CLI (gh) to be installed and available at the specified path. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$GhPath ) begin { Write-EnhancedLog -Message "Starting Authenticate-GitHubCLI function" -Level "NOTICE" # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters } process { try { Write-EnhancedLog -Message "Authenticating with GitHub CLI..." -Level "INFO" # Prompt user to choose the authentication method $choice = Read-Host "Select authentication method: 1) Enter GitHub token manually 2) Use secrets file in `$PSScriptRoot" if ($choice -eq '1') { # Option 1: Enter GitHub token manually $secureToken = Read-Host "Enter your GitHub token" -AsSecureString $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureToken) $token = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr) [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) Write-EnhancedLog -Message "Using manually entered GitHub token for authentication." -Level "INFO" } elseif ($choice -eq '2') { # Option 2: Use secrets file in $PSScriptRoot $secretsFilePath = Join-Path -Path $PSScriptRoot -ChildPath "secrets.psd1" if (-not (Test-Path -Path $secretsFilePath)) { $errorMessage = "Secrets file not found at path: $secretsFilePath" Write-EnhancedLog -Message $errorMessage -Level "ERROR" throw $errorMessage } $secrets = Import-PowerShellDataFile -Path $secretsFilePath $token = $secrets.GitHubToken if (-not $token) { $errorMessage = "GitHub token not found in the secrets file." Write-EnhancedLog -Message $errorMessage -Level "ERROR" throw $errorMessage } Write-EnhancedLog -Message "Using GitHub token from secrets file for authentication." -Level "INFO" } else { $errorMessage = "Invalid selection. Please choose 1 or 2." Write-EnhancedLog -Message $errorMessage -Level "ERROR" throw $errorMessage } # Check if GitHub CLI is already authenticated $authArguments = @("auth", "status", "-h", "github.com") $authStatus = & $GhPath $authArguments 2>&1 if ($authStatus -notlike "*Logged in to github.com*") { Write-EnhancedLog -Message "GitHub CLI is not authenticated. Attempting authentication using selected method..." -Level "WARNING" # Authenticate using the selected method $loginArguments = @("auth", "login", "--with-token") echo $token | & $GhPath $loginArguments # Re-check the authentication status $authStatus = & $GhPath $authArguments 2>&1 if ($authStatus -like "*Logged in to github.com*") { Write-EnhancedLog -Message "GitHub CLI successfully authenticated." -Level "INFO" } else { $errorMessage = "Failed to authenticate GitHub CLI. Please check the token and try again." Write-EnhancedLog -Message $errorMessage -Level "ERROR" throw $errorMessage } } else { Write-EnhancedLog -Message "GitHub CLI is already authenticated." -Level "INFO" } } catch { Write-EnhancedLog -Message "An error occurred during GitHub CLI authentication: $($_.Exception.Message)" -Level "ERROR" throw $_ } } end { Write-EnhancedLog -Message "Authenticate-GitHubCLI function execution completed." -Level "NOTICE" } } #EndRegion '.\Public\Authenticate-GitHubCLI.ps1' 110 #Region '.\Public\Check-ModuleVersionStatus.ps1' -1 function Check-ModuleVersionStatus { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]]$ModuleNames ) #the following modules PowerShellGet and PackageManagement has to be either automatically imported or manually imported into C:\windows\System32\WindowsPowerShell\v1.0\Modules Import-Module -Name PowerShellGet -ErrorAction SilentlyContinue # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PowerShellGet\PSModule.psm1' -ErrorAction SilentlyContinue # Import-Module 'C:\windows\System32\WindowsPowerShell\v1.0\Modules\PowerShellGet\PSModule.psm1' -ErrorAction SilentlyContinue # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PackageManagement\PackageProviderFunctions.psm1' -ErrorAction SilentlyContinue # Import-Module 'C:\windows\System32\WindowsPowerShell\v1.0\Modules\PackageManagement\PackageProviderFunctions.psm1' -ErrorAction SilentlyContinue # Import-Module 'C:\Program Files (x86)\WindowsPowerShell\Modules\PackageManagement\PackageManagement.psm1' -ErrorAction SilentlyContinue $results = [System.Collections.Generic.List[PSObject]]::new() # Initialize a List to hold the results foreach ($ModuleName in $ModuleNames) { try { Write-Host 'Checking module '$ModuleName $installedModule = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version -Descending | Select-Object -First 1 # $installedModule = Check-SystemWideModule -ModuleName 'Pester' $latestModule = Find-Module -Name $ModuleName -ErrorAction SilentlyContinue if ($installedModule -and $latestModule) { if ($installedModule.Version -lt $latestModule.Version) { $results.Add([PSCustomObject]@{ ModuleName = $ModuleName Status = "Outdated" InstalledVersion = $installedModule.Version LatestVersion = $latestModule.Version }) } else { $results.Add([PSCustomObject]@{ ModuleName = $ModuleName Status = "Up-to-date" InstalledVersion = $installedModule.Version LatestVersion = $installedModule.Version }) } } elseif (-not $installedModule) { $results.Add([PSCustomObject]@{ ModuleName = $ModuleName Status = "Not Installed" InstalledVersion = $null LatestVersion = $null }) } else { $results.Add([PSCustomObject]@{ ModuleName = $ModuleName Status = "Not Found in Gallery" InstalledVersion = $null LatestVersion = $null }) } } catch { Write-Error "An error occurred checking module '$ModuleName': $_" } } return $results } # Example usage: # $versionStatuses = Check-ModuleVersionStatus -ModuleNames @('Pester', 'AzureRM', 'PowerShellGet') # $versionStatuses | Format-Table -AutoSize # Display the results in a table format for readability #EndRegion '.\Public\Check-ModuleVersionStatus.ps1' 72 #Region '.\Public\CheckAndElevate.ps1' -1 function CheckAndElevate { <# .SYNOPSIS Checks if the script is running with administrative privileges and optionally elevates it if not. .DESCRIPTION The CheckAndElevate function checks whether the current PowerShell session is running with administrative privileges. It can either return the administrative status or attempt to elevate the script if it is not running as an administrator. .PARAMETER ElevateIfNotAdmin If set to $true, the function will attempt to elevate the script if it is not running with administrative privileges. If set to $false, the function will simply return the administrative status without taking any action. .EXAMPLE CheckAndElevate -ElevateIfNotAdmin $true Checks the current session for administrative privileges and elevates if necessary. .EXAMPLE $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false if (-not $isAdmin) { Write-Host "The script is not running with administrative privileges." } Checks the current session for administrative privileges and returns the status without elevating. .NOTES If the script is elevated, it will restart with administrative privileges. Ensure that any state or data required after elevation is managed appropriately. #> [CmdletBinding()] param ( [bool]$ElevateIfNotAdmin = $true ) Begin { Write-EnhancedLog -Message "Starting CheckAndElevate function" -Level "NOTICE" # Use .NET classes for efficiency try { $isAdmin = [System.Security.Principal.WindowsPrincipal]::new([System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) Write-EnhancedLog -Message "Checking for administrative privileges..." -Level "INFO" } catch { Write-EnhancedLog -Message "Error determining administrative status: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } Process { if (-not $isAdmin) { if ($ElevateIfNotAdmin) { try { Write-EnhancedLog -Message "The script is not running with administrative privileges. Attempting to elevate..." -Level "WARNING" $powerShellPath = Get-PowerShellPath $startProcessParams = @{ FilePath = $powerShellPath ArgumentList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$PSCommandPath`"") Verb = "RunAs" } Start-Process @startProcessParams Write-EnhancedLog -Message "Script re-launched with administrative privileges. Exiting current session." -Level "INFO" exit } catch { Write-EnhancedLog -Message "Failed to elevate privileges: $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } else { Write-EnhancedLog -Message "The script is not running with administrative privileges and will continue without elevation." -Level "INFO" } } else { Write-EnhancedLog -Message "Script is already running with administrative privileges." -Level "INFO" } } End { Write-EnhancedLog -Message "Exiting CheckAndElevate function" -Level "NOTICE" return $isAdmin } } # Example usage to check and optionally elevate: # CheckAndElevate -ElevateIfNotAdmin $true # Example usage to just check and return status without elevating: # $isAdmin = CheckAndElevate -ElevateIfNotAdmin $false #EndRegion '.\Public\CheckAndElevate.ps1' 93 #Region '.\Public\Clone-EnhancedRepos.ps1' -1 function Clone-EnhancedRepos { <# .SYNOPSIS Clones all repositories from a GitHub account that start with the word "Enhanced" to a specified directory using GitHub CLI. .DESCRIPTION This function uses GitHub CLI to list and clone repositories from a GitHub account that start with "Enhanced" into the specified directory. .PARAMETER githubUsername The GitHub username to retrieve repositories from. .PARAMETER targetDirectory The directory to clone the repositories into. .EXAMPLE Clone-EnhancedRepos -githubUsername "aollivierre" -targetDirectory "C:\Code\modules-beta4" Clones all repositories starting with "Enhanced" from the specified GitHub account to the target directory. .NOTES This function requires GitHub CLI (gh) and git to be installed and available in the system's PATH. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$githubUsername, [Parameter(Mandatory = $true)] [string]$targetDirectory ) begin { Write-EnhancedLog -Message "Starting Clone-EnhancedRepos function" -Level "Notice" # Create the target directory if it doesn't exist if (-not (Test-Path -Path $targetDirectory)) { Write-EnhancedLog -Message "Creating target directory: $targetDirectory" -Level "INFO" New-Item -Path $targetDirectory -ItemType Directory } } process { try { # Get the Git executable path Write-EnhancedLog -Message "Attempting to find Git executable path..." -Level "INFO" $gitPath = Get-GitPath if (-not $gitPath) { throw "Git executable not found. Please install Git or ensure it is in your PATH." } Write-EnhancedLog -Message "Git found at: $gitPath" -Level "INFO" # Set the GitHub CLI path $ghPath = "C:\Program Files\GitHub CLI\gh.exe" # Define arguments for GitHub CLI as an array $ghArguments = @("repo", "list", "aollivierre", "--json", "name,url") # Set the path to GitHub CLI executable $ghPath = "C:\Program Files\GitHub CLI\gh.exe" # Authenticate with GitHub CLI Authenticate-GitHubCLI -GhPath $ghPath # Execute the GitHub CLI command using the argument array Write-EnhancedLog -Message "Retrieving repositories for user $githubUsername using GitHub CLI..." -Level "INFO" $reposJson = & $ghPath $ghArguments Write-EnhancedLog -Message "Raw GitHub CLI output: $reposJson" -Level "DEBUG" if (-not $reposJson) { throw "No repositories found or an error occurred while retrieving repositories." } $repos = $reposJson | ConvertFrom-Json Write-EnhancedLog -Message "Converted JSON output: $repos" -Level "DEBUG" $filteredRepos = $repos | Where-Object { $_.name -like "Enhanced*" } if ($filteredRepos.Count -eq 0) { Write-EnhancedLog -Message "No repositories found that match 'Enhanced*'." -Level "WARNING" } Write-EnhancedLog -Message "Filtered repositories count: $($filteredRepos.Count)" -Level "INFO" # Clone each repository using the full path to Git foreach ($repo in $filteredRepos) { $repoName = $repo.name $repoTargetPath = Join-Path -Path $targetDirectory -ChildPath $repoName # Check if the repository already exists in the target directory if (Test-Path $repoTargetPath) { Write-EnhancedLog -Message "Repository $repoName already exists in $repoTargetPath. Skipping clone." -Level "INFO" continue } $repoCloneUrl = $repo.url # Define arguments for Git as an array $gitArguments = @("clone", $repoCloneUrl, $repoTargetPath) Write-EnhancedLog -Message "Cloning repository $repoName to $repoTargetPath..." -Level "INFO" & $gitPath $gitArguments if ($LASTEXITCODE -ne 0) { throw "Failed to clone repository $repoName. Git returned exit code $LASTEXITCODE." } Write-EnhancedLog -Message "Successfully cloned repository $repoName." -Level "INFO" } Write-EnhancedLog -Message "Cloning process completed." -Level "INFO" } catch { Write-EnhancedLog -Message "Error during cloning process: $_" -Level "ERROR" throw $_ } } end { Write-EnhancedLog -Message "Clone-EnhancedRepos function execution completed." -Level "Notice" } } #EndRegion '.\Public\Clone-EnhancedRepos.ps1' 127 #Region '.\Public\Convert-WindowsPathToLinuxPath.ps1' -1 function Convert-WindowsPathToLinuxPath { <# .SYNOPSIS Converts a Windows file path to a Linux file path. .DESCRIPTION This function takes a Windows file path as input and converts it to a Linux file path. It replaces backslashes with forward slashes and handles the drive letter. .PARAMETER WindowsPath The full file path in Windows format that needs to be converted. .EXAMPLE PS> Convert-WindowsPathToLinuxPath -WindowsPath 'C:\Code\CB\Entra\ARH\Get-EntraConnectSyncErrorsfromEntra copy.ps1' Returns '/mnt/c/Code/CB/Entra/ARH/Get-EntraConnectSyncErrorsfromEntra copy.ps1' #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$WindowsPath ) Begin { Write-Host "Starting the path conversion process..." } Process { try { Write-Host "Input Windows Path: $WindowsPath" # Replace backslashes with forward slashes $linuxPath = $WindowsPath -replace '\\', '/' # Handle drive letter by converting "C:" to "/mnt/c" if ($linuxPath -match '^[A-Za-z]:') { $driveLetter = $linuxPath.Substring(0, 1).ToLower() $linuxPath = "/mnt/$driveLetter" + $linuxPath.Substring(2) } Write-Host "Converted Linux Path: $linuxPath" return $linuxPath } catch { Write-Host "Error during conversion: $_" throw } } End { Write-Host "Path conversion completed." } } # # Example usage # $windowsPath = 'C:\Code\Unified365toolbox\Graph\graphcert.pfx' # $linuxPath = Convert-WindowsPathToLinuxPath -WindowsPath $windowsPath # Write-Host "Linux path: $linuxPath" #EndRegion '.\Public\Convert-WindowsPathToLinuxPath.ps1' 59 #Region '.\Public\Download-Modules.ps1' -1 function Download-Modules { param ( [array]$scriptDetails # Array of script details, including URLs ) $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new() foreach ($scriptDetail in $scriptDetails) { $process = Invoke-WebScript -url $scriptDetail.Url if ($process) { $processList.Add($process) } } # Optionally wait for all processes to complete foreach ($process in $processList) { $process.WaitForExit() } } #EndRegion '.\Public\Download-Modules.ps1' 21 #Region '.\Public\Download-Psd1File.ps1' -1 function Download-Psd1File { <# .SYNOPSIS Downloads a PSD1 file from a specified URL and saves it to a local destination. .DESCRIPTION This function downloads a PowerShell Data file (PSD1) from a given URL and saves it to the specified local path. If the download fails, an error is logged and the function throws an exception. .PARAMETER url The URL of the PSD1 file to be downloaded. .PARAMETER destinationPath The local path where the PSD1 file will be saved. .EXAMPLE Download-Psd1File -url "https://example.com/modules.psd1" -destinationPath "$env:TEMP\modules.psd1" Downloads the PSD1 file from the specified URL and saves it to the provided local path. .NOTES This function requires internet access to download the PSD1 file from the specified URL. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$url, [Parameter(Mandatory = $true)] [string]$destinationPath ) begin { Write-EnhancedLog -Message "Starting Download-Psd1File function" -Level "NOTICE" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters # Validate destination directory $destinationDirectory = [System.IO.Path]::GetDirectoryName($destinationPath) if (-not (Test-Path -Path $destinationDirectory)) { Write-EnhancedLog -Message "Destination directory not found at path: $destinationDirectory" -Level "ERROR" throw "Destination directory not found." } Write-EnhancedLog -Message "Validated destination directory at path: $destinationDirectory" -Level "INFO" } process { try { Write-EnhancedLog -Message "Downloading PSD1 file from URL: $url" -Level "INFO" Invoke-WebRequest -Uri $url -OutFile $destinationPath -UseBasicParsing Write-EnhancedLog -Message "Downloaded PSD1 file to: $destinationPath" -Level "INFO" } catch { Write-EnhancedLog -Message "Failed to download PSD1 file from $url. Error: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } end { Write-EnhancedLog -Message "Download-Psd1File function execution completed." -Level "NOTICE" } } #EndRegion '.\Public\Download-Psd1File.ps1' 64 #Region '.\Public\Elevate-Script.ps1' -1 function Elevate-Script { if (-not (Test-Admin)) { Write-EnhancedLog "Restarting script with elevated permissions..." $startProcessParams = @{ FilePath = "powershell.exe" ArgumentList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath) Verb = "RunAs" } Start-Process @startProcessParams exit } } #EndRegion '.\Public\Elevate-Script.ps1' 13 #Region '.\Public\Ensure-GitIsInstalled.ps1' -1 function Ensure-GitIsInstalled { param ( [version]$MinVersion = [version]"2.46.0", [string]$RegistryPath = "HKLM:\SOFTWARE\GitForWindows", [string]$ExePath = "C:\Program Files\Git\bin\git.exe" ) Write-EnhancedLog -Message "Checking if Git is installed and meets the minimum version requirement." -Level "INFO" # Use the Validate-SoftwareInstallation function to check if Git is installed and meets the version requirement $validationResult = Validate-SoftwareInstallation -SoftwareName "Git" -MinVersion $MinVersion -RegistryPath $RegistryPath -ExePath $ExePath if ($validationResult.IsInstalled) { Write-EnhancedLog -Message "Git version $($validationResult.Version) is installed and meets the minimum version requirement." -Level "INFO" return $true } else { Write-EnhancedLog -Message "Git is not installed or does not meet the minimum version requirement. Installing Git..." -Level "WARNING" $installSuccess = Install-GitFromWeb return $installSuccess } } #EndRegion '.\Public\Ensure-GitIsInstalled.ps1' 23 #Region '.\Public\Ensure-LoggingFunctionExists.ps1' -1 function Ensure-LoggingFunctionExists { param ( # [string]$LoggingFunctionName = "Write-EnhancedLog" [string]$LoggingFunctionName ) if (Get-Command $LoggingFunctionName -ErrorAction SilentlyContinue) { Write-EnhancedLog -Message "Logging works" -Level "INFO" -ForegroundColor ([ConsoleColor]::Green) } else { throw "$LoggingFunctionName function not found. Terminating script." } } # Example of how to call the function with the default parameter # Ensure-LoggingFunctionExists # Example of how to call the function with a different logging function name # Ensure-LoggingFunctionExists -LoggingFunctionName "Write-EnhancedLog" #EndRegion '.\Public\Ensure-LoggingFunctionExists.ps1' 20 #Region '.\Public\Ensure-ModuleIsLatest.ps1' -1 function Ensure-ModuleIsLatest { param ( [string]$ModuleName ) Write-EnhancedLog -Message "Checking if the latest version of $ModuleName is installed..." -Level "INFO" try { if ($SkipCheckandElevate) { Write-EnhancedLog -Message "Skipping CheckAndElevate due to SkipCheckandElevate parameter." -Level "INFO" } else { CheckAndElevate -ElevateIfNotAdmin $true } Invoke-InPowerShell5 Reset-ModulePaths # Get the installed version of the module, if any $installedModule = Get-Module -Name $ModuleName -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1 # Get the latest available version of the module from PSGallery $latestModule = Find-Module -Name $ModuleName if ($installedModule) { if ($installedModule.Version -lt $latestModule.Version) { Write-EnhancedLog -Message "$ModuleName version $($installedModule.Version) is installed, but version $($latestModule.Version) is available. Updating module..." -Level "WARNING" Install-Module -Name $ModuleName -Scope AllUsers -Force -SkipPublisherCheck -Verbose } else { Write-EnhancedLog -Message "The latest version of $ModuleName is already installed. Version: $($installedModule.Version)" -Level "INFO" } } else { Write-EnhancedLog -Message "$ModuleName is not installed. Installing the latest version $($latestModule.Version)..." -Level "WARNING" Install-Module -Name $ModuleName -Scope AllUsers -Force -SkipPublisherCheck -Verbose -AllowClobber } } catch { Write-EnhancedLog -Message "Error occurred while checking or installing $ModuleName $_" -Level "ERROR" throw } } #EndRegion '.\Public\Ensure-ModuleIsLatest.ps1' 46 #Region '.\Public\Ensure-NuGetProvider.ps1' -1 function Ensure-NuGetProvider { # Ensure NuGet provider and PowerShellGet module are installed if running in PowerShell 5 if ($PSVersionTable.PSVersion.Major -eq 5) { if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) { Install-PackageProvider -Name NuGet -Force -Confirm:$false Install-Module -Name PowerShellGet -Force -AllowClobber Write-EnhancedLog "NuGet provider installed successfully." -Level "INFO" } else { Write-EnhancedLog "NuGet provider is already installed." -Level "INFO" } } else { Write-EnhancedLog "This script is running in PowerShell version $($PSVersionTable.PSVersion) which is not version 5. No action is taken for NuGet" -Level "INFO" } } #EndRegion '.\Public\Ensure-NuGetProvider.ps1' 17 #Region '.\Public\Generate-SoftwareInstallSummaryReport.ps1' -1 function Generate-SoftwareInstallSummaryReport { param ( [PSCustomObject[]]$installationResults ) $totalSoftware = $installationResults.Count $successfulInstallations = $installationResults | Where-Object { $_.Status -eq "Successfully Installed" } $alreadyInstalled = $installationResults | Where-Object { $_.Status -eq "Already Installed" } $failedInstallations = $installationResults | Where-Object { $_.Status -like "Failed*" } Write-Host "Total Software: $totalSoftware" -ForegroundColor Cyan Write-Host "Successful Installations: $($successfulInstallations.Count)" -ForegroundColor Green Write-Host "Already Installed: $($alreadyInstalled.Count)" -ForegroundColor Yellow Write-Host "Failed Installations: $($failedInstallations.Count)" -ForegroundColor Red # Detailed Summary Write-Host "`nDetailed Summary:" -ForegroundColor Cyan $installationResults | ForEach-Object { Write-Host "Software: $($_.SoftwareName)" -ForegroundColor White Write-Host "Status: $($_.Status)" -ForegroundColor White Write-Host "Version Found: $($_.VersionFound)" -ForegroundColor White Write-Host "----------------------------------------" -ForegroundColor Gray } } #EndRegion '.\Public\Generate-SoftwareInstallSummaryReport.ps1' 25 #Region '.\Public\Get-GitPath.ps1' -1 function Get-GitPath { <# .SYNOPSIS Discovers the path to the Git executable on the system. .DESCRIPTION This function attempts to find the Git executable by checking common installation directories and the system's PATH environment variable. .EXAMPLE $gitPath = Get-GitPath if ($gitPath) { Write-Host "Git found at: $gitPath" } else { Write-Host "Git not found." } #> [CmdletBinding()] param () try { # Common Git installation paths $commonPaths = @( "C:\Program Files\Git\bin\git.exe", "C:\Program Files (x86)\Git\bin\git.exe" ) # Check the common paths foreach ($path in $commonPaths) { if (Test-Path -Path $path) { Write-EnhancedLog -Message "Git found at: $path" -Level "INFO" return $path } } # If not found, check if Git is in the system PATH $gitPathInEnv = (Get-Command git -ErrorAction SilentlyContinue).Source if ($gitPathInEnv) { Write-EnhancedLog -Message "Git found in system PATH: $gitPathInEnv" -Level "INFO" return $gitPathInEnv } # If Git is still not found, return $null Write-EnhancedLog -Message "Git executable not found." -Level "ERROR" return $null } catch { Write-EnhancedLog -Message "Error occurred while trying to find Git path: $_" -Level "ERROR" return $null } } #EndRegion '.\Public\Get-GitPath.ps1' 52 #Region '.\Public\Get-ModulesScriptPathsAndVariables.ps1' -1 # function Get-ModulesScriptPathsAndVariables { # <# # .SYNOPSIS # Dot-sources all PowerShell scripts in the 'Modules' folder relative to the script root. # .DESCRIPTION # This function finds all PowerShell (.ps1) scripts in a 'Modules' folder located in the script root directory and dot-sources them. It logs the process, including any errors encountered, with optional color coding. # .EXAMPLE # Dot-SourceModulesScripts # Dot-sources all scripts in the 'Modules' folder and logs the process. # .NOTES # Ensure the Write-EnhancedLog function is defined before using this function for logging purposes. # #> # param ( # [string]$BaseDirectory # ) # try { # $ModulesFolderPath = Join-Path -Path $BaseDirectory -ChildPath "Modules" # if (-not (Test-Path -Path $ModulesFolderPath)) { # throw "Modules folder path does not exist: $ModulesFolderPath" # } # # Construct and return a PSCustomObject # return [PSCustomObject]@{ # BaseDirectory = $BaseDirectory # ModulesFolderPath = $ModulesFolderPath # } # } # catch { # Write-Host "Error in finding Modules script files: $_" -ForegroundColor Red # # Optionally, you could return a PSCustomObject indicating an error state # # return [PSCustomObject]@{ Error = $_.Exception.Message } # } # } #EndRegion '.\Public\Get-ModulesScriptPathsAndVariables.ps1' 42 #Region '.\Public\Get-ParentScriptName.ps1' -1 function Get-ParentScriptName { [CmdletBinding()] param () try { # Get the current call stack $callStack = Get-PSCallStack # If there is a call stack, return the top-most script name if ($callStack.Count -gt 0) { foreach ($frame in $callStack) { if ($frame.ScriptName) { $parentScriptName = $frame.ScriptName # Write-EnhancedLog -Message "Found script in call stack: $parentScriptName" -Level "INFO" } } if (-not [string]::IsNullOrEmpty($parentScriptName)) { $parentScriptName = [System.IO.Path]::GetFileNameWithoutExtension($parentScriptName) return $parentScriptName } } # If no script name was found, return 'UnknownScript' Write-EnhancedLog -Message "No script name found in the call stack." -Level "WARNING" return "UnknownScript" } catch { Write-EnhancedLog -Message "An error occurred while retrieving the parent script name: $_" -Level "ERROR" return "UnknownScript" } } #EndRegion '.\Public\Get-ParentScriptName.ps1' 33 #Region '.\Public\Get-Platform.ps1' -1 function Get-Platform { if ($PSVersionTable.PSVersion.Major -ge 7) { return $PSVersionTable.Platform } else { return [System.Environment]::OSVersion.Platform } } #EndRegion '.\Public\Get-Platform.ps1' 9 #Region '.\Public\Get-PowerShellPath.ps1' -1 function Get-PowerShellPath { <# .SYNOPSIS Retrieves the path to the installed PowerShell executable, defaulting to PowerShell 5. .DESCRIPTION This function checks for the existence of PowerShell 5 and PowerShell 7 on the system. By default, it returns the path to PowerShell 5 unless the -UsePS7 switch is provided. If the specified version is not found, an error is thrown. .PARAMETER UsePS7 Optional switch to prioritize PowerShell 7 over PowerShell 5. .EXAMPLE $pwshPath = Get-PowerShellPath Write-Host "PowerShell found at: $pwshPath" .EXAMPLE $pwshPath = Get-PowerShellPath -UsePS7 Write-Host "PowerShell found at: $pwshPath" .NOTES Author: Abdullah Ollivierre Date: 2024-08-15 #> [CmdletBinding()] param ( [switch]$UsePS7 ) Begin { Write-EnhancedLog -Message "Starting Get-PowerShellPath function" -Level "NOTICE" } Process { $pwsh7Path = "C:\Program Files\PowerShell\7\pwsh.exe" $pwsh5Path = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" if ($UsePS7) { if (Test-Path $pwsh7Path) { Write-EnhancedLog -Message "PowerShell 7 found at $pwsh7Path" -Level "INFO" return $pwsh7Path } elseif (Test-Path $pwsh5Path) { Write-EnhancedLog -Message "PowerShell 7 not found, falling back to PowerShell 5 at $pwsh5Path" -Level "WARNING" return $pwsh5Path } } else { if (Test-Path $pwsh5Path) { Write-EnhancedLog -Message "PowerShell 5 found at $pwsh5Path" -Level "INFO" return $pwsh5Path } elseif (Test-Path $pwsh7Path) { Write-EnhancedLog -Message "PowerShell 5 not found, falling back to PowerShell 7 at $pwsh7Path" -Level "WARNING" return $pwsh7Path } } $errorMessage = "Neither PowerShell 7 nor PowerShell 5 was found on this system." Write-EnhancedLog -Message $errorMessage -Level "ERROR" throw $errorMessage } End { Write-EnhancedLog -Message "Exiting Get-PowerShellPath function" -Level "NOTICE" } } # # Get the path to the installed PowerShell executable # try { # $pwshPath = Get-PowerShellPath # Write-Host "PowerShell executable found at: $pwshPath" # # Example: Start a new PowerShell session using the found path # Start-Process -FilePath $pwshPath -ArgumentList "-NoProfile", "-Command", "Get-Process" -NoNewWindow -Wait # } # catch { # Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red # } #EndRegion '.\Public\Get-PowerShellPath.ps1' 81 #Region '.\Public\Handle-Error.ps1' -1 function Handle-Error { param ( [Parameter(Mandatory = $true)] [System.Management.Automation.ErrorRecord]$ErrorRecord ) try { if ($PSVersionTable.PSVersion.Major -ge 7) { $fullErrorDetails = Get-Error -InputObject $ErrorRecord | Out-String } else { $fullErrorDetails = $ErrorRecord.Exception | Format-List * -Force | Out-String } Write-EnhancedLog -Message "Exception Message: $($ErrorRecord.Exception.Message)" -Level "ERROR" Write-EnhancedLog -Message "Full Exception: $fullErrorDetails" -Level "ERROR" } catch { # Fallback error handling in case of an unexpected error in the try block Write-EnhancedLog -Message "An error occurred while handling another error. Original Exception: $($ErrorRecord.Exception.Message)" -Level "CRITICAL" Write-EnhancedLog -Message "Handler Exception: $($_.Exception.Message)" -Level "CRITICAL" Write-EnhancedLog -Message "Handler Full Exception: $($_ | Out-String)" -Level "CRITICAL" } } # # Example usage of Handle-Error # try { # # Intentionally cause an error for demonstration purposes # throw "This is a test error" # } catch { # Handle-Error -ErrorRecord $_ # } #EndRegion '.\Public\Handle-Error.ps1' 34 #Region '.\Public\Import-EnhancedModules.ps1' -1 function Import-EnhancedModules { param ( [string]$modulePsd1Path, # Path to the PSD1 file containing the list of modules to install and import [string]$ScriptPath # Path to the PSD1 file containing the list of modules to install and import ) # Validate PSD1 file path if (-not (Test-Path -Path $modulePsd1Path)) { Write-EnhancedLog "modules.psd1 file not found at path: $modulePsd1Path" -Level "ERROR" throw "modules.psd1 file not found." } # Check if we need to re-launch in PowerShell 5 Invoke-InPowerShell5 -ScriptPath $ScriptPath # If running in PowerShell 5, reset the module paths and proceed with the rest of the script Reset-ModulePaths # Import the PSD1 data $moduleData = Import-PowerShellDataFile -Path $modulePsd1Path $modulesToImport = $moduleData.requiredModules foreach ($moduleName in $modulesToImport) { if (-not (Get-Module -ListAvailable -Name $moduleName)) { Write-EnhancedLog "Module $moduleName is not installed. Attempting to install..." -Level "INFO" Install-EnhancedModule -ModuleName $moduleName -ScriptPath $ScriptPath } Write-EnhancedLog "Importing module: $moduleName" -Level "INFO" try { Import-Module -Name $moduleName -Verbose:$true -Force:$true -Global:$true } catch { Write-EnhancedLog "Failed to import module $moduleName. Error: $_" -Level "ERROR" } } } #EndRegion '.\Public\Import-EnhancedModules.ps1' 39 #Region '.\Public\Import-Modules.ps1' -1 function Import-Modules { param ( [Parameter(Mandatory = $true)] [string[]]$Modules ) foreach ($module in $Modules) { if (Get-Module -ListAvailable -Name $module) { # Import-Module -Name $module -Force -Verbose Import-Module -Name $module -Force:$true -Global:$true Write-EnhancedLog -Message "Module '$module' imported." -Level "INFO" } else { Write-EnhancedLog -Message "Module '$module' not found. Cannot import." -Level "ERROR" } } } #EndRegion '.\Public\Import-Modules.ps1' 18 #Region '.\Public\Import-ModulesFromLocalRepository.ps1' -1 function Import-ModulesFromLocalRepository { <# .SYNOPSIS Imports all modules found in the specified Modules directory. .DESCRIPTION This function scans the Modules directory for module folders and attempts to import the module. If a module file is not found or if importing fails, appropriate error messages are logged. .PARAMETER ModulesFolderPath The path to the folder containing the modules. .PARAMETER ScriptPath The path to the script directory containing the exclusion file. .EXAMPLE Import-ModulesFromLocalRepository -ModulesFolderPath "C:\code\Modules" -ScriptPath "C:\scripts" This example imports all modules found in the specified Modules directory. #> [CmdletBinding()] param ( [string]$ModulesFolderPath # [string]$ScriptPath ) Begin { # Get the path to the Modules directory $moduleDirectories = Get-ChildItem -Path $ModulesFolderPath -Directory Write-Host "Module directories found: $($moduleDirectories.Count)" -ForegroundColor ([ConsoleColor]::Cyan) # Read the modules exclusion list from the JSON file # $exclusionFilePath = Join-Path -Path $ScriptPath -ChildPath "modulesexclusion.json" # if (Test-Path -Path $exclusionFilePath) { # $excludedModules = Get-Content -Path $exclusionFilePath | ConvertFrom-Json # Write-Host "Excluded modules: $excludedModules" -ForegroundColor ([ConsoleColor]::Cyan) # } else { # $excludedModules = @() # Write-Host "No exclusion file found. Proceeding with all modules." -ForegroundColor ([ConsoleColor]::Yellow) # } } Process { foreach ($moduleDir in $moduleDirectories) { # Skip the module if it is in the exclusion list if ($excludedModules -contains $moduleDir.Name) { Write-Host "Skipping excluded module: $($moduleDir.Name)" -ForegroundColor ([ConsoleColor]::Yellow) continue } # Construct the path to the module file $modulePath = Join-Path -Path $moduleDir.FullName -ChildPath "$($moduleDir.Name).psm1" # Check if the module file exists if (Test-Path -Path $modulePath) { # Import the module with retry logic try { Import-ModuleWithRetry -ModulePath $modulePath Write-Host "Successfully imported module: $($moduleDir.Name)" -ForegroundColor ([ConsoleColor]::Green) } catch { Write-Host "Failed to import module: $($moduleDir.Name). Error: $_" -ForegroundColor ([ConsoleColor]::Red) } } else { Write-Host "Module file not found: $modulePath" -ForegroundColor ([ConsoleColor]::Red) } } } End { Write-Host "Module import process completed." -ForegroundColor ([ConsoleColor]::Cyan) } } #EndRegion '.\Public\Import-ModulesFromLocalRepository.ps1' 76 #Region '.\Public\Import-ModuleWithRetry.ps1' -1 function Import-ModuleWithRetry { <# .SYNOPSIS Imports a PowerShell module with retries on failure. .DESCRIPTION This function attempts to import a specified PowerShell module, retrying the import process up to a specified number of times upon failure. It also checks if the module path exists before attempting to import. .PARAMETER ModulePath The path to the PowerShell module file (.psm1) that should be imported. .PARAMETER MaxRetries The maximum number of retries to attempt if importing the module fails. Default is 3. .PARAMETER WaitTimeSeconds The number of seconds to wait between retry attempts. Default is 2 seconds. .EXAMPLE $modulePath = "C:\Modules\MyPowerShellModule.psm1" Import-ModuleWithRetry -ModulePath $modulePath Tries to import the module located at "C:\Modules\MyPowerShellModule.psm1", with up to 3 retries, waiting 2 seconds between each retry. .NOTES This function requires the `Write-EnhancedLog` function to be defined in the script for logging purposes. .LINK Write-EnhancedLog #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string]$ModulePath, [int]$MaxRetries = 3, [int]$WaitTimeSeconds = 2 ) Begin { $retryCount = 0 $isModuleLoaded = $false Write-Host "Starting to import module from path: $ModulePath" # Check if the module file exists before attempting to load it if (-not (Test-Path -Path $ModulePath -PathType Leaf)) { Write-Host "The module path '$ModulePath' does not exist." return } } Process { while (-not $isModuleLoaded -and $retryCount -lt $MaxRetries) { try { # Import-Module $ModulePath -ErrorAction Stop -Verbose -Global Import-Module $ModulePath -ErrorAction Stop -Global -Force:$true # Import-Module $ModulePath -ErrorAction Stop $isModuleLoaded = $true Write-Host "Module: $ModulePath imported successfully." } catch { $errorMsg = $_.Exception.Message Write-Host "Attempt $retryCount to load module failed: $errorMsg Waiting $WaitTimeSeconds seconds before retrying." Write-Host "Attempt $retryCount to load module failed with error: $errorMsg" Start-Sleep -Seconds $WaitTimeSeconds } finally { $retryCount++ } if ($retryCount -eq $MaxRetries -and -not $isModuleLoaded) { Write-Host "Failed to import module after $MaxRetries retries." Write-Host "Failed to import module after $MaxRetries retries with last error: $errorMsg" break } } } End { if ($isModuleLoaded) { Write-Host "Module: $ModulePath loaded successfully." } else { Write-Host -Message "Failed to load module $ModulePath within the maximum retry limit." } } } #EndRegion '.\Public\Import-ModuleWithRetry.ps1' 89 #Region '.\Public\Initialize-Environment.ps1' -1 function Initialize-Environment { param ( [string]$Mode, # Accepts either 'dev' or 'prod' # [string]$WindowsModulePath, # Path to the Windows module [string]$ModulesBasePath, # Custom modules base path, [PSCustomObject[]]$scriptDetails ) if ($Mode -eq "dev") { $gitInstalled = Ensure-GitIsInstalled if ($gitInstalled) { Write-EnhancedLog -Message "Git installation check completed successfully." -Level "INFO" } else { Write-EnhancedLog -Message "Failed to install Git." -Level "ERROR" } if (-not $SkipGitRepos) { Manage-GitRepositories -ModulesBasePath 'C:\Code\modulesv2' Write-EnhancedLog -Message "Git repose checked successfully." -Level "INFO" } else { Write-EnhancedLog -Message "Skipping Git Repos" -Level "INFO" } $DBG # Call Setup-GlobalPaths with custom paths Setup-GlobalPaths -ModulesBasePath $ModulesBasePath # Check if the directory exists and contains any files (not just the directory existence) if (-Not (Test-Path "$global:modulesBasePath\*.*")) { Write-EnhancedLog "Modules not found or directory is empty at $global:modulesBasePath. Initiating download..." -Level "INFO" # Download-Modules -scriptDetails $scriptDetails if (-not $SkipEnhancedModules) { # Download-Modules -scriptDetails $scriptDetails # Example usage: Call the main function with the script details Invoke-CloneEnhancedRepos -scriptDetails $scriptDetails Write-EnhancedLog -Message "Modules downloaded successfully." -Level "INFO" } else { Write-EnhancedLog -Message "Skipping module download as per the provided parameter." -Level "INFO" } # The rest of your script continues here... # Re-check after download attempt if (-Not (Test-Path "$global:modulesBasePath\*.*")) { throw "Download failed or the modules were not placed in the expected directory." } } else { Write-EnhancedLog "Source Modules already exist at $global:modulesBasePath" -Level "INFO" } # The following block will ONLY run in dev mode # Construct the paths dynamically using the base paths # $modulePath = Join-Path -Path $global:modulesBasePath -ChildPath $WindowsModulePath # $global:modulePath = $modulePath # # Re-check that the module exists before attempting to import # if (-Not (Test-Path $global:modulePath)) { # throw "The specified module '$global:modulePath' does not exist after download. Cannot import module." # } # # Import the module using the dynamically constructed path # Import-Module -Name $global:modulePath -Verbose -Force:$true -Global:$true # # Log the paths to verify # Write-EnhancedLog "Module Path: $global:modulePath" -Level "INFO" # Write-Host "Starting to call Import-LatestModulesLocalRepository..." # Import-ModulesFromLocalRepository -ModulesFolderPath $global:modulesBasePath } elseif ($Mode -eq "prod") { # Log the start of the process Write-EnhancedLog "Production mode selected. Importing modules..." -Level "INFO" # Path to the current script # $ScriptPath = $MyInvocation.MyCommand.Definition # Re-launch the script in PowerShell 5 if not already running in PS5 Invoke-InPowerShell5 -ScriptPath $ScriptPath # Reset the module paths and proceed with the rest of the script in PS5 Reset-ModulePaths # Ensure NuGet provider is installed Ensure-NuGetProvider # # Install essential modules # Install-Module -Name EnhancedBoilerPlateAO -Force -SkipPublisherCheck -Scope AllUsers -Verbose # Install-Module -Name EnhancedLoggingAO -Force -SkipPublisherCheck -Scope AllUsers -Verbose # Ensure that the latest versions of the essential modules are installed Ensure-ModuleIsLatest -ModuleName "PSFramework" # Ensure-ModuleIsLatest -ModuleName "EnhancedBoilerPlateAO" # Ensure-ModuleIsLatest -ModuleName "EnhancedLoggingAO" # Define the PSD1 file URLs and local paths $psd1Url = "https://raw.githubusercontent.com/aollivierre/module-starter/main/Enhanced-modules.psd1" $localPsd1Path = "$env:TEMP\enhanced-modules.psd1" # Download the PSD1 file Download-Psd1File -url $psd1Url -destinationPath $localPsd1Path # Install and import modules based on the PSD1 file InstallAndImportModulesPSGallery -modulePsd1Path $localPsd1Path # Handle third-party PS Gallery modules if ($SkipPSGalleryModules) { Write-EnhancedLog "Skipping third-party PS Gallery Modules" -Level "INFO" } else { Write-EnhancedLog "Starting PS Gallery Module installation" -Level "INFO" # Re-launch the script in PowerShell 5 if not already running in PS5 Invoke-InPowerShell5 -ScriptPath $ScriptPath # Reset the module paths in PS5 Reset-ModulePaths # Ensure NuGet provider is installed Ensure-NuGetProvider # Download and process the third-party modules PSD1 file $psd1Url = "https://raw.githubusercontent.com/aollivierre/module-starter/main/modules.psd1" $localPsd1Path = "$env:TEMP\modules.psd1" Download-Psd1File -url $psd1Url -destinationPath $localPsd1Path InstallAndImportModulesPSGallery -modulePsd1Path $localPsd1Path } } } #EndRegion '.\Public\Initialize-Environment.ps1' 151 #Region '.\Public\Install-EnhancedModule.ps1' -1 function Install-EnhancedModule { param ( [string]$ModuleName ) # Log the start of the module installation process Write-EnhancedLog "Starting the module installation process for: $ModuleName" -Level "NOTICE" # Check if the current PowerShell version is not 5 # if ($PSVersionTable.PSVersion.Major -ne 5) { # Write-EnhancedLog "Current PowerShell version is $($PSVersionTable.PSVersion). PowerShell 5 is required." -Level "WARNING" # # Get the path to PowerShell 5 # $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" # Write-EnhancedLog "PowerShell 5 path: $ps5Path" -Level "INFO" # # Construct the command to install the module in PowerShell 5 # $command = "& '$ps5Path' -ExecutionPolicy Bypass -Command `"Install-Module -Name '$ModuleName' -Force -SkipPublisherCheck -Scope AllUsers`"" # Write-EnhancedLog "Constructed command for PowerShell 5: $command" -Level "DEBUG" # # Launch PowerShell 5 to run the module installation # Write-EnhancedLog "Launching PowerShell 5 to install the module: $ModuleName" -Level "INFO" # Invoke-Expression $command # Write-EnhancedLog "Module installation command executed in PowerShell 5. Exiting current session." -Level "NOTICE" # return # Path to the current script # $ScriptPath = $MyInvocation.MyCommand.Definition # # Check if we need to re-launch in PowerShell 5 # Invoke-InPowerShell5 -ScriptPath $ScriptPath # # If running in PowerShell 5, reset the module paths and proceed with the rest of the script # Reset-ModulePaths # } # If already in PowerShell 5, install the module Write-EnhancedLog "Current PowerShell version is 5. Proceeding with module installation." -Level "INFO" Write-EnhancedLog "Installing module: $ModuleName in PowerShell 5" -Level "NOTICE" try { Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers Write-EnhancedLog "Module $ModuleName installed successfully in PowerShell 5." -Level "INFO" } catch { Write-EnhancedLog "Failed to install module $ModuleName. Error: $_" -Level "ERROR" } } #EndRegion '.\Public\Install-EnhancedModule.ps1' 51 #Region '.\Public\Install-GitFromWeb.ps1' -1 function Install-GitFromWeb { param ( [string]$url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-Git.ps1" ) Write-EnhancedLog -Message "Attempting to install Git from URL: $url" -Level "INFO" $process = Invoke-WebScript -url $url if ($process) { $process.WaitForExit() # Perform post-installation validation $validationParams = @{ SoftwareName = "Git" MinVersion = [version]"2.46.0" RegistryPath = "HKLM:\SOFTWARE\GitForWindows" ExePath = "C:\Program Files\Git\bin\git.exe" MaxRetries = 3 # Single retry after installation DelayBetweenRetries = 5 } $postValidationResult = Validate-SoftwareInstallation @validationParams if ($postValidationResult.IsInstalled -and $postValidationResult.Version -ge $validationParams.MinVersion) { Write-EnhancedLog -Message "Git successfully installed and validated." -Level "INFO" return $true } else { Write-EnhancedLog -Message "Git installation validation failed." -Level "ERROR" return $false } } else { Write-EnhancedLog -Message "Failed to start the installation process for Git." -Level "ERROR" return $false } } #EndRegion '.\Public\Install-GitFromWeb.ps1' 37 #Region '.\Public\Install-Modules.ps1' -1 function Install-Modules { param ( [Parameter(Mandatory = $true)] [string[]]$Modules ) # Check if running in PowerShell 5 or in a Windows environment # if ($PSVersionTable.PSVersion.Major -eq 5 -or ($PSVersionTable.Platform -eq 'Win32NT' -or [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT)) { # # Install the NuGet package provider if the condition is met # Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser # } if ($PSVersionTable.PSVersion.Major -eq 5) { # Install the NuGet package provider if the condition is met Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser } foreach ($module in $Modules) { if (-not (Get-Module -ListAvailable -Name $module)) { # Install-Module -Name $module -Force -Scope AllUsers Install-Module -Name $module -Force -Scope CurrentUser Write-EnhancedLog -Message "Module '$module' installed." -Level "INFO" -ForegroundColor } else { Write-EnhancedLog -Message "Module '$module' is already installed." -Level "INFO" -ForegroundColor } } } #EndRegion '.\Public\Install-Modules.ps1' 30 #Region '.\Public\Install-ModuleWithPowerShell5Fallback.ps1' -1 function Install-ModuleWithPowerShell5Fallback { param ( [string]$ModuleName ) # Log the start of the module installation process Write-Enhancedlog "Starting the module installation process for: $ModuleName" -Level "NOTICE" $DBG # Check if the current PowerShell version is not 5 if ($PSVersionTable.PSVersion.Major -ne 5) { Write-Enhancedlog "Current PowerShell version is $($PSVersionTable.PSVersion). PowerShell 5 is required." -Level "WARNING" # # Get the path to PowerShell 5 # $ps5Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" # Write-Enhancedlog "PowerShell 5 path: $ps5Path" -Level "INFO" # # Construct the parameters for Start-Process # $startProcessParams = @{ # FilePath = $ps5Path # ArgumentList = @( # "-NoExit", # "-NoProfile", # "-ExecutionPolicy", "Bypass", # "-Command", "Install-Module -Name '$ModuleName' -Force -SkipPublisherCheck -Scope AllUsers" # ) # Verb = "RunAs" # PassThru = $true # } # Write-Enhancedlog "Constructed Start-Process parameters for PowerShell 5: $($startProcessParams | Out-String)" -Level "DEBUG" # # Launch PowerShell 5 to run the module installation # Write-Enhancedlog "Launching PowerShell 5 to install the module: $ModuleName" -Level "INFO" # $DBG # $process = Start-Process @startProcessParams # Write-Enhancedlog "Module installation command executed in PowerShell 5. Exiting current session." -Level "NOTICE" # return } # If already in PowerShell 5, install the module Write-Enhancedlog "Current PowerShell version is 5. Proceeding with module installation." -Level "INFO" Write-Enhancedlog "Installing module: $ModuleName in PowerShell 5" -Level "NOTICE" try { Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers Write-Enhancedlog "Module $ModuleName installed successfully in PowerShell 5." -Level "INFO" } catch { Write-Enhancedlog "Failed to install module $ModuleName. Error: $_" -Level "ERROR" } } #EndRegion '.\Public\Install-ModuleWithPowerShell5Fallback.ps1' 57 #Region '.\Public\Install-PowerShell7FromWeb.ps1' -1 function Install-PowerShell7FromWeb { param ( [string]$url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-PowerShell7.ps1" ) Write-EnhancedLog -Message "Attempting to install PowerShell 7 from URL: $url" -Level "INFO" $process = Invoke-WebScript -url $url if ($process) { $process.WaitForExit() # Perform post-installation validation $validationParams = @{ SoftwareName = "PowerShell" MinVersion = [version]"7.4.4" RegistryPath = "HKLM:\SOFTWARE\Microsoft\PowerShellCore" ExePath = "C:\Program Files\PowerShell\7\pwsh.exe" MaxRetries = 3 # Single retry after installation DelayBetweenRetries = 5 } $postValidationResult = Validate-SoftwareInstallation @validationParams if ($postValidationResult.IsInstalled -and $postValidationResult.Version -ge $validationParams.MinVersion) { Write-EnhancedLog -Message "PowerShell 7 successfully installed and validated." -Level "INFO" return $true } else { Write-EnhancedLog -Message "PowerShell 7 installation validation failed." -Level "ERROR" return $false } } else { Write-EnhancedLog -Message "Failed to start the installation process for PowerShell 7." -Level "ERROR" return $false } } #EndRegion '.\Public\Install-PowerShell7FromWeb.ps1' 38 #Region '.\Public\Install-RequiredModules.ps1' -1 # function Install-RequiredModules { # [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 # # $requiredModules = @("Microsoft.Graph", "Microsoft.Graph.Authentication") # $requiredModules = @("Microsoft.Graph.Authentication") # foreach ($module in $requiredModules) { # if (!(Get-Module -ListAvailable -Name $module)) { # Write-EnhancedLog -Message "Installing module: $module" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) # Install-Module -Name $module -Force # Write-EnhancedLog -Message "Module: $module has been installed" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) # } # else { # Write-EnhancedLog -Message "Module $module is already installed" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) # } # } # $ImportedModules = @("Microsoft.Graph.Identity.DirectoryManagement", "Microsoft.Graph.Authentication") # foreach ($Importedmodule in $ImportedModules) { # if ((Get-Module -ListAvailable -Name $Importedmodule)) { # Write-EnhancedLog -Message "Importing module: $Importedmodule" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) # Import-Module -Name $Importedmodule # Write-EnhancedLog -Message "Module: $Importedmodule has been Imported" -Level "INFO" -ForegroundColor ([ConsoleColor]::Cyan) # } # } # } #EndRegion '.\Public\Install-RequiredModules.ps1' 33 #Region '.\Public\InstallAndImportModulesPSGallery.ps1' -1 function InstallAndImportModulesPSGallery { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$modulePsd1Path ) begin { Write-EnhancedLog -Message "Starting InstallAndImportModulesPSGallery function" -Level "INFO" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters # Initialize counters and lists for summary $moduleSuccessCount = 0 $moduleFailCount = 0 $successModules = [System.Collections.Generic.List[PSCustomObject]]::new() $failedModules = [System.Collections.Generic.List[PSCustomObject]]::new() # Validate PSD1 file path if (-not (Test-Path -Path $modulePsd1Path)) { Write-EnhancedLog -Message "modules.psd1 file not found at path: $modulePsd1Path" -Level "ERROR" throw "modules.psd1 file not found." } Write-EnhancedLog -Message "Found modules.psd1 file at path: $modulePsd1Path" -Level "INFO" } process { try { # Read and import PSD1 data $moduleData = Import-PowerShellDataFile -Path $modulePsd1Path $requiredModules = $moduleData.RequiredModules $importedModules = $moduleData.ImportedModules $myModules = $moduleData.MyModules # Validate, Install, and Import Modules if ($requiredModules) { Write-EnhancedLog -Message "Installing required modules: $($requiredModules -join ', ')" -Level "INFO" foreach ($moduleName in $requiredModules) { try { Update-ModuleIfOldOrMissing -ModuleName $moduleName $moduleInfo = Get-Module -Name $moduleName -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1 $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = $moduleInfo.Version Path = $moduleInfo.ModuleBase } $successModules.Add($moduleDetails) Write-EnhancedLog -Message "Successfully installed/updated module: $moduleName" -Level "INFO" $moduleSuccessCount++ } catch { $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = "N/A" Path = "N/A" } $failedModules.Add($moduleDetails) Write-EnhancedLog -Message "Failed to install/update module: $moduleName. Error: $_" -Level "ERROR" $moduleFailCount++ } } Write-EnhancedLog "All module update processes have completed." -Level "NOTICE" } if ($importedModules) { Write-EnhancedLog -Message "Importing modules: $($importedModules -join ', ')" -Level "INFO" foreach ($moduleName in $importedModules) { try { Import-Module -Name $moduleName -Force $moduleInfo = Get-Module -Name $moduleName | Select-Object -First 1 $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = $moduleInfo.Version Path = $moduleInfo.ModuleBase } $successModules.Add($moduleDetails) Write-EnhancedLog -Message "Successfully imported module: $moduleName" -Level "INFO" $moduleSuccessCount++ } catch { $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = "N/A" Path = "N/A" } $failedModules.Add($moduleDetails) Write-EnhancedLog -Message "Failed to import module: $moduleName. Error: $_" -Level "ERROR" $moduleFailCount++ } } } if ($myModules) { Write-EnhancedLog -Message "Importing custom modules: $($myModules -join ', ')" -Level "INFO" foreach ($moduleName in $myModules) { try { Import-Module -Name $moduleName -Force $moduleInfo = Get-Module -Name $moduleName | Select-Object -First 1 $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = $moduleInfo.Version Path = $moduleInfo.ModuleBase } $successModules.Add($moduleDetails) Write-EnhancedLog -Message "Successfully imported custom module: $moduleName" -Level "INFO" $moduleSuccessCount++ } catch { $moduleDetails = [PSCustomObject]@{ Name = $moduleName Version = "N/A" Path = "N/A" } $failedModules.Add($moduleDetails) Write-EnhancedLog -Message "Failed to import custom module: $moduleName. Error: $_" -Level "ERROR" $moduleFailCount++ } } } Write-EnhancedLog -Message "Modules installation and import process completed." -Level "INFO" } catch { Write-EnhancedLog -Message "Error processing modules.psd1: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ throw $_ } } end { # Output summary report Write-EnhancedLog -Message "InstallAndImportModulesPSGallery function execution completed." -Level "INFO" Write-Host "---------- Summary Report ----------" -ForegroundColor Cyan Write-Host "Total Modules Processed: $($moduleSuccessCount + $moduleFailCount)" -ForegroundColor Cyan Write-Host "Modules Successfully Processed: $moduleSuccessCount" -ForegroundColor Green Write-Host "Modules Failed: $moduleFailCount" -ForegroundColor Red if ($successModules.Count -gt 0) { Write-Host "Successful Modules:" -ForegroundColor Green $successModules | Format-Table -Property Name, Version, Path -AutoSize | Out-String | Write-Host } if ($failedModules.Count -gt 0) { Write-Host "Failed Modules:" -ForegroundColor Red $failedModules | Format-Table -Property Name, Version, Path -AutoSize | Out-String | Write-Host } Write-Host "-----------------------------------" -ForegroundColor Cyan } } #EndRegion '.\Public\InstallAndImportModulesPSGallery.ps1' 152 #Region '.\Public\Invoke-CloneEnhancedRepos.ps1' -1 function Invoke-CloneEnhancedRepos { param ( [PSCustomObject[]]$scriptDetails ) try { # Elevate script if needed Elevate-Script # Initialize necessary variables $powerShellPath = Get-PowerShellPath $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new() $installationResults = [System.Collections.Generic.List[PSCustomObject]]::new() # Process each software detail foreach ($detail in $scriptDetails) { Process-SoftwareDetails -detail $detail -powerShellPath $powerShellPath -installationResults ([ref]$installationResults) -processList ([ref]$processList) } # Validate installation results after processes finish Validate-InstallationResults -processList ([ref]$processList) -installationResults ([ref]$installationResults) -scriptDetails $scriptDetails # Generate the final summary report Generate-SoftwareInstallSummaryReport -installationResults $installationResults # Example invocation to clone repositories: Clone-EnhancedRepos -githubUsername "aollivierre" -targetDirectory "C:\Code\modulesv2" } catch { # Capture the error details $errorDetails = $_ | Out-String Write-EnhancedLog "An error occurred: $errorDetails" -Level "ERROR" throw } } #EndRegion '.\Public\Invoke-CloneEnhancedRepos.ps1' 36 #Region '.\Public\Invoke-GitCommandWithRetry.ps1' -1 function Invoke-GitCommandWithRetry { param ( [string]$GitPath, [string]$Arguments, [int]$MaxRetries = 3, [int]$DelayBetweenRetries = 5 ) for ($i = 0; $i -lt $MaxRetries; $i++) { try { # Split the arguments string into an array for correct parsing $argumentArray = $Arguments -split ' ' $output = & "$GitPath" @argumentArray if ($output -match "fatal:") { Write-EnhancedLog -Message "Git command failed: $output" -Level "WARNING" if ($i -lt ($MaxRetries - 1)) { Write-EnhancedLog -Message "Retrying in $DelayBetweenRetries seconds..." -Level "INFO" Start-Sleep -Seconds $DelayBetweenRetries } else { Write-EnhancedLog -Message "Git command failed after $MaxRetries retries." -Level "ERROR" throw "Git command failed: $output" } } else { return $output } } catch { Write-EnhancedLog -Message "Error executing Git command: $_" -Level "ERROR" if ($i -lt ($MaxRetries - 1)) { Write-EnhancedLog -Message "Retrying in $DelayBetweenRetries seconds..." -Level "INFO" Start-Sleep -Seconds $DelayBetweenRetries } else { throw $_ } } } } #EndRegion '.\Public\Invoke-GitCommandWithRetry.ps1' 42 #Region '.\Public\Invoke-InPowerShell5.ps1' -1 function Invoke-InPowerShell5 { param ( [string]$ScriptPath ) if ($PSVersionTable.PSVersion.Major -ne 5) { Write-EnhancedLog -Message "Relaunching script in PowerShell 5 (x64)..." -Level "WARNING" # Get the path to PowerShell 5 (x64) $ps5x64Path = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" # Launch in PowerShell 5 (x64) $startProcessParams64 = @{ FilePath = $ps5x64Path ArgumentList = @( "-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$ScriptPath`"" ) Verb = "RunAs" PassThru = $true } Write-EnhancedLog -Message "Starting PowerShell 5 (x64) to perform the update..." -Level "NOTICE" $process64 = Start-Process @startProcessParams64 $process64.WaitForExit() Write-EnhancedLog -Message "PowerShell 5 (x64) process completed." -Level "NOTICE" Exit } } #EndRegion '.\Public\Invoke-InPowerShell5.ps1' 33 #Region '.\Public\Invoke-ModuleStarter.ps1' -1 function Invoke-ModuleStarter { param ( [string]$Mode = "dev", [bool]$SkipPSGalleryModules = $false, [bool]$SkipCheckandElevate = $false, [bool]$SkipPowerShell7Install = $false, [bool]$SkipEnhancedModules = $false, [bool]$SkipGitRepos = $false ) # Log the parameters Write-Host "The Module Starter script is running in mode: $Mode" Write-Host "The Module Starter SkipPSGalleryModules is set to: $SkipPSGalleryModules" Write-Host "The Module Starter SkipCheckandElevate is set to: $SkipCheckandElevate" Write-Host "The Module Starter SkipPowerShell7Install is set to: $SkipPowerShell7Install" Write-Host "The Module Starter SkipEnhancedModules is set to: $SkipEnhancedModules" Write-Host "The Module Starter SkipGitRepos is set to: $SkipGitRepos" # Report the current PowerShell version $psVersion = $PSVersionTable.PSVersion Write-Host "Current PowerShell Version: $psVersion" -ForegroundColor Green Write-Host "Full PowerShell Version Details:" $PSVersionTable | Format-Table -AutoSize # $processList = [System.Collections.Generic.List[System.Diagnostics.Process]]::new() # $ScriptPath = $MyInvocation.MyCommand.Definition # $global:currentStep = 0 # Install PSFramework module if not installed if (-not $SkipPSGalleryModules) { Write-Host "Installing PSFramework module..." -ForegroundColor Cyan Install-Module -Name PSFramework -Scope AllUsers -Force -AllowClobber -SkipPublisherCheck -Verbose } # Define script details for initialization $initializeParams = @{ Mode = $Mode WindowsModulePath = "EnhancedBoilerPlateAO\EnhancedBoilerPlateAO.psm1" ModulesBasePath = "C:\code\modulesv2" scriptDetails = @( @{ Url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-Git.ps1"; SoftwareName = "Git"; MinVersion = [version]"2.41.0.0" }, @{ Url = "https://raw.githubusercontent.com/aollivierre/setuplab/main/Install-GitHubCLI.ps1"; SoftwareName = "GitHub CLI"; MinVersion = [version]"2.54.0" } ) } # Check and elevate permissions if required if (-not $SkipCheckandElevate) { Write-EnhancedLog -Message "Checking and elevating permissions if necessary." -Level "INFO" CheckAndElevate -ElevateIfNotAdmin $true } else { Write-EnhancedLog -Message "Skipping CheckAndElevate due to SkipCheckandElevate parameter." -Level "INFO" } # Initialize environment based on the mode and other parameters Write-Host "Initializing environment..." -ForegroundColor Cyan Initialize-Environment @initializeParams # Setup logging Write-EnhancedLog -Message "Script Started in $Mode mode" -Level "INFO" } #EndRegion '.\Public\Invoke-ModuleStarter.ps1' 62 #Region '.\Public\Invoke-WebScript.ps1' -1 function Invoke-WebScript { param ( [string]$url ) $powerShellPath = Get-PowerShellPath -ForcePowerShell5 Write-EnhancedLog -Message "Validating URL: $url" -Level "INFO" if (Test-Url -url $url) { Write-EnhancedLog -Message "Running script from URL: $url" -Level "INFO" $startProcessParams = @{ FilePath = $powerShellPath ArgumentList = @( "-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "Invoke-Expression (Invoke-RestMethod -Uri '$url')" ) Verb = "RunAs" PassThru = $true } $process = Start-Process @startProcessParams return $process } else { Write-EnhancedLog -Message "URL $url is not accessible" -Level "ERROR" return $null } } #EndRegion '.\Public\Invoke-WebScript.ps1' 35 #Region '.\Public\Is-ServerCore.ps1' -1 function Is-ServerCore { $explorerPath = "$env:SystemRoot\explorer.exe" if (Test-Path $explorerPath) { return $false } else { return $true } } Is-ServerCore #EndRegion '.\Public\Is-ServerCore.ps1' 12 #Region '.\Public\Log-Params.ps1' -1 function Log-Params { <# .SYNOPSIS Logs the provided parameters and their values with the parent function name appended. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable]$Params ) Begin { # Get the name of the parent function $parentFunctionName = (Get-PSCallStack)[1].Command # Write-EnhancedLog -Message "Starting Log-Params function in $parentFunctionName" -Level "INFO" } Process { try { foreach ($key in $Params.Keys) { # Append the parent function name to the key $enhancedKey = "$parentFunctionName.$key" Write-EnhancedLog -Message "$enhancedKey $($Params[$key])" -Level "INFO" } } catch { Write-EnhancedLog -Message "An error occurred while logging parameters in $parentFunctionName $($_.Exception.Message)" -Level "ERROR" Handle-Error -ErrorRecord $_ } } End { # Write-EnhancedLog -Message "Exiting Log-Params function in $parentFunctionName" -Level "INFO" } } #EndRegion '.\Public\Log-Params.ps1' 36 #Region '.\Public\Log-Step.ps1' -1 function Log-Step { $global:currentStep++ $totalSteps = $global:steps.Count $stepDescription = $global:steps[$global:currentStep - 1].Description Write-Host "Step [$global:currentStep/$totalSteps]: $stepDescription" -ForegroundColor Cyan } #EndRegion '.\Public\Log-Step.ps1' 7 #Region '.\Public\Manage-GitRepositories.ps1' -1 function Manage-GitRepositories { param ( [Parameter(Mandatory = $true)] [string]$ModulesBasePath ) begin { Write-EnhancedLog -Message "Starting Manage-GitRepositories function" -Level "INFO" # Initialize lists for tracking repository statuses $reposWithPushChanges = [System.Collections.Generic.List[string]]::new() $reposSummary = [System.Collections.Generic.List[PSCustomObject]]::new() # Validate ModulesBasePath if (-not (Test-Path -Path $ModulesBasePath)) { Write-EnhancedLog -Message "Modules base path not found: $ModulesBasePath" -Level "ERROR" throw "Modules base path not found." } Write-EnhancedLog -Message "Found modules base path: $ModulesBasePath" -Level "INFO" # Get the Git path $GitPath = Get-GitPath if (-not $GitPath) { throw "Git executable not found." } # Set environment variable to avoid interactive Git prompts $env:GIT_TERMINAL_PROMPT = "0" } process { try { $repos = Get-ChildItem -Path $ModulesBasePath -Directory foreach ($repo in $repos) { Set-Location -Path $repo.FullName # Add the repository to Git's safe directories $repoPath = $repo.FullName $arguments = "config --global --add safe.directory `"$repoPath`"" Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments $arguments # Remove any existing .gitconfig.lock file to avoid rename prompt $lockFilePath = "$HOME\.gitconfig.lock" if (Test-Path $lockFilePath) { Remove-Item $lockFilePath -Force Write-EnhancedLog -Message "Removed .gitconfig.lock file for repository $($repo.Name)" -Level "INFO" } # Fetch the latest changes with a retry mechanism Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments "fetch" # Check for pending changes $arguments = "status" Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments $arguments if ($status -match "fatal:") { Write-EnhancedLog -Message "Error during status check in repository $($repo.Name): $status" -Level "ERROR" continue } $repoStatus = "Up to Date" if ($status -match "Your branch is behind") { Write-EnhancedLog -Message "Repository $($repo.Name) is behind the remote. Pulling changes..." -Level "INFO" # Pull changes if needed Invoke-GitCommandWithRetry -GitPath $GitPath -Arguments "pull" $repoStatus = "Pulled" } if ($status -match "Your branch is ahead") { Write-EnhancedLog -Message "Repository $($repo.Name) has unpushed changes." -Level "WARNING" $reposWithPushChanges.Add($repo.FullName) $repoStatus = "Pending Push" } # Add the repository status to the summary list $reposSummary.Add([pscustomobject]@{ RepositoryName = $repo.Name Status = $repoStatus LastChecked = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") }) } # Summary of repositories with pending push changes if ($reposWithPushChanges.Count -gt 0) { Write-EnhancedLog -Message "The following repositories have pending push changes:" -Level "WARNING" $reposWithPushChanges | ForEach-Object { Write-EnhancedLog -Message $_ -Level "WARNING" } Write-EnhancedLog -Message "Please manually commit and push the changes in these repositories." -Level "WARNING" } else { Write-EnhancedLog -Message "All repositories are up to date." -Level "INFO" } } catch { Write-EnhancedLog -Message "An error occurred while managing Git repositories: $_" -Level "ERROR" throw $_ } } end { # Summary output in the console with color coding $totalRepos = $reposSummary.Count $pulledRepos = $reposSummary | Where-Object { $_.Status -eq "Pulled" } $pendingPushRepos = $reposSummary | Where-Object { $_.Status -eq "Pending Push" } $upToDateRepos = $reposSummary | Where-Object { $_.Status -eq "Up to Date" } Write-Host "---------- Summary Report ----------" -ForegroundColor Cyan Write-Host "Total Repositories: $totalRepos" -ForegroundColor Cyan Write-Host "Repositories Pulled: $($pulledRepos.Count)" -ForegroundColor Green Write-Host "Repositories with Pending Push: $($pendingPushRepos.Count)" -ForegroundColor Yellow Write-Host "Repositories Up to Date: $($upToDateRepos.Count)" -ForegroundColor Green # Return to the original location Set-Location -Path $ModulesBasePath Write-EnhancedLog -Message "Manage-GitRepositories function execution completed." -Level "INFO" } } #EndRegion '.\Public\Manage-GitRepositories.ps1' 125 #Region '.\Public\Process-SoftwareDetails.ps1' -1 function Process-SoftwareDetails { param ( [PSCustomObject]$detail, [string]$powerShellPath, [ref]$installationResults, [ref]$processList ) $url = $detail.Url $softwareName = $detail.SoftwareName $minVersion = $detail.MinVersion $registryPath = $detail.RegistryPath # Validate the existing installation Write-EnhancedLog "Validating existing installation of $softwareName..." $installationCheck = if ($registryPath) { Validate-SoftwareInstallation -SoftwareName $softwareName -MinVersion $minVersion -MaxRetries 3 -DelayBetweenRetries 5 -RegistryPath $registryPath } else { Validate-SoftwareInstallation -SoftwareName $softwareName -MinVersion $minVersion -MaxRetries 3 -DelayBetweenRetries 5 } if ($installationCheck.IsInstalled) { Write-EnhancedLog "$softwareName version $($installationCheck.Version) is already installed. Skipping installation." -Level "INFO" $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Already Installed"; VersionFound = $installationCheck.Version }) } else { if (Test-Url -url $url) { Log-Step Write-EnhancedLog "Running script from URL: $url" -Level "INFO" $process = Start-Process -FilePath $powerShellPath -ArgumentList @("-NoExit", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "Invoke-Expression (Invoke-RestMethod -Uri '$url')") -Verb RunAs -PassThru $processList.Value.Add($process) $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Installed"; VersionFound = "N/A" }) } else { Write-EnhancedLog "URL $url is not accessible" -Level "ERROR" $installationResults.Value.Add([pscustomobject]@{ SoftwareName = $softwareName; Status = "Failed - URL Not Accessible"; VersionFound = "N/A" }) } } } #EndRegion '.\Public\Process-SoftwareDetails.ps1' 43 #Region '.\Public\Remove-OldVersions.ps1' -1 function Remove-OldVersions { <# .SYNOPSIS Removes older versions of a specified PowerShell module. .DESCRIPTION The Remove-OldVersions function removes all but the latest version of the specified PowerShell module. It ensures that only the most recent version is retained. .PARAMETER ModuleName The name of the module for which older versions will be removed. .EXAMPLE Remove-OldVersions -ModuleName "Pester" Removes all but the latest version of the Pester module. .NOTES This function requires administrative access to manage modules and assumes that the CheckAndElevate function is defined elsewhere in the script. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$ModuleName ) begin { Write-EnhancedLog -Message "Starting Remove-OldVersions function for module: $ModuleName" -Level "INFO" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters } process { # Get all versions except the latest one $allVersions = Get-Module -ListAvailable -Name $ModuleName | Sort-Object Version $latestVersion = $allVersions | Select-Object -Last 1 $olderVersions = $allVersions | Where-Object { $_.Version -ne $latestVersion.Version } foreach ($version in $olderVersions) { try { Write-EnhancedLog -Message "Removing older version $($version.Version) of $ModuleName..." -Level "INFO" $modulePath = $version.ModuleBase Write-EnhancedLog -Message "Starting takeown and icacls for $modulePath" -Level "INFO" Write-EnhancedLog -Message "Checking and elevating to admin if needed" -Level "INFO" CheckAndElevate & takeown.exe /F $modulePath /A /R & icacls.exe $modulePath /reset & icacls.exe $modulePath /grant "*S-1-5-32-544:F" /inheritance:d /T Remove-Item -Path $modulePath -Recurse -Force -Confirm:$false Write-EnhancedLog -Message "Removed $($version.Version) successfully." -Level "INFO" } catch { Write-EnhancedLog -Message "Failed to remove version $($version.Version) of $ModuleName at $modulePath. Error: $_" -Level "ERROR" Handle-Error -ErrorRecord $_ } } } end { Write-EnhancedLog -Message "Remove-OldVersions function execution completed for module: $ModuleName" -Level "INFO" } } #EndRegion '.\Public\Remove-OldVersions.ps1' 62 #Region '.\Public\Reset-ModulePaths.ps1' -1 function Reset-ModulePaths { [CmdletBinding()] param () begin { # Initialization block, typically used for setup tasks Write-EnhancedLog -Message "Initializing Reset-ModulePaths function..." -Level "DEBUG" } process { try { # Log the start of the process Write-EnhancedLog -Message "Resetting module paths to default values..." -Level "INFO" # Get the current user's Documents path $userModulesPath = [System.IO.Path]::Combine($env:USERPROFILE, 'Documents\WindowsPowerShell\Modules') # Define the default module paths $defaultModulePaths = @( "C:\Program Files\WindowsPowerShell\Modules", $userModulesPath, "C:\Windows\System32\WindowsPowerShell\v1.0\Modules" ) # Attempt to reset the PSModulePath environment variable $env:PSModulePath = [string]::Join(';', $defaultModulePaths) Write-EnhancedLog -Message "PSModulePath successfully set to: $($env:PSModulePath -split ';' | Out-String)" -Level "INFO" # Optionally persist the change for the current user [Environment]::SetEnvironmentVariable("PSModulePath", $env:PSModulePath, [EnvironmentVariableTarget]::User) Write-EnhancedLog -Message "PSModulePath environment variable set for the current user." -Level "INFO" } catch { # Capture and log any errors that occur during the process $errorMessage = $_.Exception.Message Write-EnhancedLog -Message "Error resetting module paths: $errorMessage" -Level "ERROR" # Optionally, you could throw the error to halt the script throw $_ } } end { # Finalization block, typically used for cleanup tasks Write-EnhancedLog -Message "Reset-ModulePaths function completed." -Level "DEBUG" } } #EndRegion '.\Public\Reset-ModulePaths.ps1' 48 #Region '.\Public\Sanitize-VersionString.ps1' -1 function Sanitize-VersionString { param ( [string]$versionString ) try { # Remove any non-numeric characters and additional segments like ".windows" $sanitizedVersion = $versionString -replace '[^0-9.]', '' -replace '\.\.+', '.' # Convert to System.Version $version = [version]$sanitizedVersion return $version } catch { Write-EnhancedLog -Message "Failed to convert version string: $versionString. Error: $_" -Level "ERROR" return $null } } #EndRegion '.\Public\Sanitize-VersionString.ps1' 19 #Region '.\Public\Setup-GlobalPaths.ps1' -1 function Setup-GlobalPaths { param ( [string]$ModulesBasePath # Path to the modules directory ) # Set the modules base path and create if it doesn't exist if (-Not (Test-Path $ModulesBasePath)) { Write-EnhancedLog "ModulesBasePath '$ModulesBasePath' does not exist. Creating directory..." -Level "INFO" New-Item -Path $ModulesBasePath -ItemType Directory -Force } $global:modulesBasePath = $ModulesBasePath # Log the paths for verification Write-EnhancedLog "Modules Base Path: $global:modulesBasePath" -Level "INFO" } #EndRegion '.\Public\Setup-GlobalPaths.ps1' 17 #Region '.\Public\Test-Admin.ps1' -1 function Test-Admin { $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) return $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } #EndRegion '.\Public\Test-Admin.ps1' 5 #Region '.\Public\Test-Url.ps1' -1 function Test-Url { param ( [string]$url ) try { Invoke-RestMethod -Uri $url -Method Head -ErrorAction Stop return $true } catch { return $false } } #EndRegion '.\Public\Test-Url.ps1' 13 #Region '.\Public\Update-ModuleIfOldOrMissing.ps1' -1 function Update-ModuleIfOldOrMissing { <# .SYNOPSIS Updates or installs a specified PowerShell module if it is outdated or missing. .DESCRIPTION The Update-ModuleIfOldOrMissing function checks the status of a specified PowerShell module and updates it if it is outdated. If the module is not installed, it installs the latest version. It also removes older versions after the update. .PARAMETER ModuleName The name of the module to be checked and updated or installed. .EXAMPLE Update-ModuleIfOldOrMissing -ModuleName "Pester" Checks and updates the Pester module if it is outdated or installs it if not present. .NOTES This function requires administrative access to manage modules and assumes that the CheckAndElevate, Check-ModuleVersionStatus, and Remove-OldVersions functions are defined elsewhere in the script. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$ModuleName ) begin { Write-EnhancedLog -Message "Starting Update-ModuleIfOldOrMissing function for module: $ModuleName" -Level "Notice" Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters } process { $moduleStatus = Check-ModuleVersionStatus -ModuleNames @($ModuleName) foreach ($status in $moduleStatus) { switch ($status.Status) { "Outdated" { Write-EnhancedLog -Message "Updating $ModuleName from version $($status.InstalledVersion) to $($status.LatestVersion)." -Level "WARNING" # Remove older versions Remove-OldVersions -ModuleName $ModuleName # Install the latest version of the module # Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers Install-ModuleWithPowerShell5Fallback -ModuleName $ModuleName Write-EnhancedLog -Message "$ModuleName has been updated to the latest version." -Level "INFO" } "Up-to-date" { Write-EnhancedLog -Message "$ModuleName version $($status.InstalledVersion) is up-to-date. No update necessary." -Level "INFO" Remove-OldVersions -ModuleName $ModuleName } "Not Installed" { Write-EnhancedLog -Message "$ModuleName is not installed. Installing the latest version..." -Level "WARNING" # Install-Module -Name $ModuleName -Force -SkipPublisherCheck -Scope AllUsers $DBG Install-ModuleWithPowerShell5Fallback -ModuleName $ModuleName Write-EnhancedLog -Message "$ModuleName has been installed." -Level "INFO" } "Not Found in Gallery" { Write-EnhancedLog -Message "Unable to find '$ModuleName' in the PowerShell Gallery." -Level "ERROR" } } } } end { Write-EnhancedLog -Message "Update-ModuleIfOldOrMissing function execution completed for module: $ModuleName" -Level "Notice" } } #EndRegion '.\Public\Update-ModuleIfOldOrMissing.ps1' 70 #Region '.\Public\Validate-InstallationResults.ps1' -1 function Validate-InstallationResults { param ( [ref]$processList, [ref]$installationResults, [PSCustomObject[]]$scriptDetails ) # Wait for all processes to complete foreach ($process in $processList.Value) { $process.WaitForExit() } # Post-installation validation foreach ($result in $installationResults.Value) { if ($result.Status -eq "Installed") { if ($result.SoftwareName -in @("RDP", "Windows Terminal")) { Write-EnhancedLog "Skipping post-installation validation for $($result.SoftwareName)." -Level "INFO" $result.Status = "Successfully Installed" continue } Write-EnhancedLog "Validating installation of $($result.SoftwareName)..." $validationResult = Validate-SoftwareInstallation -SoftwareName $result.SoftwareName -MinVersion ($scriptDetails | Where-Object { $_.SoftwareName -eq $result.SoftwareName }).MinVersion if ($validationResult.IsInstalled) { Write-EnhancedLog "Validation successful: $($result.SoftwareName) version $($validationResult.Version) is installed." -Level "INFO" $result.VersionFound = $validationResult.Version $result.Status = "Successfully Installed" } else { Write-EnhancedLog "Validation failed: $($result.SoftwareName) was not found on the system." -Level "ERROR" $result.Status = "Failed - Not Found After Installation" } } } } #EndRegion '.\Public\Validate-InstallationResults.ps1' 37 #Region '.\Public\Validate-SoftwareInstallation.ps1' -1 function Validate-SoftwareInstallation { [CmdletBinding()] param ( [string]$SoftwareName, [version]$MinVersion = [version]"0.0.0.0", [string]$RegistryPath = "", [string]$ExePath = "", [int]$MaxRetries = 3, [int]$DelayBetweenRetries = 5 ) Begin { Write-EnhancedLog -Message "Starting Validate-SoftwareInstallation function" -Level "NOTICE" # Log-Params -Params $PSCmdlet.MyInvocation.BoundParameters } Process { $retryCount = 0 $validationSucceeded = $false while ($retryCount -lt $MaxRetries -and -not $validationSucceeded) { # Registry-based validation if ($RegistryPath -or $SoftwareName) { $registryPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall" ) if ($RegistryPath) { if (Test-Path $RegistryPath) { $app = Get-ItemProperty -Path $RegistryPath -ErrorAction SilentlyContinue if ($app -and $app.DisplayName -like "*$SoftwareName*") { $installedVersion = Sanitize-VersionString -versionString $app.DisplayVersion if ($installedVersion -ge $MinVersion) { $validationSucceeded = $true return @{ IsInstalled = $true Version = $installedVersion ProductCode = $app.PSChildName } } } } } else { foreach ($path in $registryPaths) { $items = Get-ChildItem -Path $path -ErrorAction SilentlyContinue foreach ($item in $items) { $app = Get-ItemProperty -Path $item.PsPath -ErrorAction SilentlyContinue if ($app.DisplayName -like "*$SoftwareName*") { $installedVersion = Sanitize-VersionString -versionString $app.DisplayVersion if ($installedVersion -ge $MinVersion) { $validationSucceeded = $true return @{ IsInstalled = $true Version = $installedVersion ProductCode = $app.PSChildName } } } } } } } # File-based validation if ($ExePath) { if (Test-Path $ExePath) { $appVersionString = (Get-ItemProperty -Path $ExePath).VersionInfo.ProductVersion.Split(" ")[0] # Extract only the version number $appVersion = Sanitize-VersionString -versionString $appVersionString if ($appVersion -ge $MinVersion) { Write-EnhancedLog -Message "Validation successful: $SoftwareName version $appVersion is installed at $ExePath." -Level "INFO" return @{ IsInstalled = $true Version = $appVersion Path = $ExePath } } else { Write-EnhancedLog -Message "Validation failed: $SoftwareName version $appVersion does not meet the minimum version requirement ($MinVersion)." -Level "ERROR" } } else { Write-EnhancedLog -Message "Validation failed: $SoftwareName executable was not found at $ExePath." -Level "ERROR" } } $retryCount++ Write-EnhancedLog -Message "Validation attempt $retryCount failed: $SoftwareName not found or version does not meet the minimum requirement ($MinVersion). Retrying in $DelayBetweenRetries seconds..." -Level "WARNING" Start-Sleep -Seconds $DelayBetweenRetries } return @{ IsInstalled = $false } } End { Write-EnhancedLog -Message "Exiting Validate-SoftwareInstallation function" -Level "NOTICE" } } # # Parameters for validating OneDrive installation # $oneDriveValidationParams = @{ # SoftwareName = "OneDrive" # MinVersion = [version]"24.146.0721.0003" # Example minimum version # RegistryPath = "HKLM:\SOFTWARE\Microsoft\OneDrive" # Example registry path for OneDrive metadata # ExePath = "C:\Program Files\Microsoft OneDrive\OneDrive.exe" # Path to the OneDrive executable # MaxRetries = 3 # DelayBetweenRetries = 5 # } # # Perform the validation # $oneDriveValidationResult = Validate-SoftwareInstallation @oneDriveValidationParams # # Check the results of the validation # if ($oneDriveValidationResult.IsInstalled) { # Write-Host "OneDrive version $($oneDriveValidationResult.Version) is installed and validated." -ForegroundColor Green # Write-Host "Executable Path: $($oneDriveValidationResult.Path)" # } else { # Write-Host "OneDrive is not installed or does not meet the minimum version requirement." -ForegroundColor Red # } #EndRegion '.\Public\Validate-SoftwareInstallation.ps1' 126 #Region '.\Public\Write-EnhancedLog.ps1' -1 function Write-EnhancedLog { param ( [string]$Message, [string]$Level = 'INFO' ) # Get the PowerShell call stack to determine the actual calling function $callStack = Get-PSCallStack $callerFunction = if ($callStack.Count -ge 2) { $callStack[1].Command } else { '<Unknown>' } # Prepare the formatted message with the actual calling function information $formattedMessage = "[$Level] $Message" # Map custom levels to PSFramework levels $psfLevel = switch ($Level.ToUpper()) { 'DEBUG' { 'Debug' } 'INFO' { 'Host' } 'NOTICE' { 'Important' } 'WARNING' { 'Warning' } 'ERROR' { 'Error' } 'CRITICAL' { 'Critical' } 'IMPORTANT' { 'Important' } 'OUTPUT' { 'Output' } 'SIGNIFICANT' { 'Significant' } 'VERYVERBOSE' { 'VeryVerbose' } 'VERBOSE' { 'Verbose' } 'SOMEWHATVERBOSE' { 'SomewhatVerbose' } 'SYSTEM' { 'System' } 'INTERNALCOMMENT' { 'InternalComment' } default { 'Host' } } # Log the message using PSFramework with the actual calling function name Write-PSFMessage -Level $psfLevel -Message $formattedMessage -FunctionName $callerFunction } #EndRegion '.\Public\Write-EnhancedLog.ps1' 36 #Region '.\Public\Write-EnhancedModuleStarterLog-Archive.ps1' -1 # function Write-EnhancedModuleStarterLog { # param ( # [string]$Message, # [string]$Level = "INFO" # ) # # Get the PowerShell call stack to determine the actual calling function # $callStack = Get-PSCallStack # $callerFunction = if ($callStack.Count -ge 2) { $callStack[1].Command } else { '<Unknown>' } # # Get the parent script name # $parentScriptName = Get-ParentScriptName # # Prepare the formatted message with the actual calling function information # $formattedMessage = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] [$parentScriptName.$callerFunction] $Message" # # Display the log message based on the log level using Write-Host # switch ($Level.ToUpper()) { # "DEBUG" { Write-Host $formattedMessage -ForegroundColor DarkGray } # "INFO" { Write-Host $formattedMessage -ForegroundColor Green } # "NOTICE" { Write-Host $formattedMessage -ForegroundColor Cyan } # "WARNING" { Write-Host $formattedMessage -ForegroundColor Yellow } # "ERROR" { Write-Host $formattedMessage -ForegroundColor Red } # "CRITICAL" { Write-Host $formattedMessage -ForegroundColor Magenta } # default { Write-Host $formattedMessage -ForegroundColor White } # } # # Append to log file # $logFilePath = [System.IO.Path]::Combine($env:TEMP, 'Module-Starter.log') # $formattedMessage | Out-File -FilePath $logFilePath -Append -Encoding utf8 # } #EndRegion '.\Public\Write-EnhancedModuleStarterLog-Archive.ps1' 32 |