Public/Start-cChocoEx.ps1
<#
.SYNOPSIS Bootstraps and manages the cChocoEx PowerShell DSC Module configuration. .DESCRIPTION Comprehensive function that initializes, configures, and manages the cChocoEx environment. Handles installation, configuration management, and scheduled task creation for Chocolatey package management. Key features: - Chocolatey installation and configuration - Package source management - Feature configuration - Package installation - Maintenance window management - Environment variable configuration - Scheduled task management .PARAMETER SettingsURI URI to a PowerShell Data File containing cChocoEx settings. .PARAMETER InstallDir Installation directory for Chocolatey. Defaults to "$env:ProgramData\chocolatey". .PARAMETER ChocoInstallScriptUrl URL to the Chocolatey installation script. Defaults to official source. .PARAMETER ChocoDownloadUrl Optional URL to a specific Chocolatey nupkg for installation. .PARAMETER SourcesConfig Path or URL to the Chocolatey sources configuration file. .PARAMETER PackageConfig Array of paths or URLs to package configuration files. .PARAMETER ChocoConfig Path or URL to the Chocolatey configuration file. .PARAMETER FeatureConfig Path or URL to the Chocolatey features configuration file. .PARAMETER NoCache Prevents caching of configuration files. Files are downloaded to temp location. .PARAMETER WipeCache Clears all cached configuration files before processing. .PARAMETER RandomDelay Adds a random delay (0-1800 seconds) before processing. .PARAMETER Loop Enables continuous execution through scheduled task. .PARAMETER LoopDelay Minutes to wait between loops when Loop is enabled. Defaults to 60. .PARAMETER MigrateLegacyConfigurations Migrates legacy configuration files to current format. .PARAMETER OverrideMaintenanceWindow Bypasses maintenance window restrictions. .PARAMETER EnableNotifications Enables desktop notifications for operations (Windows 10+ only). .PARAMETER SetcChocoExEnvironment Persists configuration to machine environment variables. .EXAMPLE Start-cChocoEx -SettingsURI "https://config.contoso.com/chocolatey/settings.psd1" Initializes cChocoEx using settings from a remote configuration file. .EXAMPLE Start-cChocoEx -Loop -LoopDelay 120 -RandomDelay Starts cChocoEx in continuous mode with 2-hour intervals and random startup delay. .EXAMPLE Start-cChocoEx -PackageConfig @("packages1.psd1", "packages2.psd1") -NoCache Processes multiple package configurations without caching files. .NOTES Author: Jon Yonke Version: 2.0 Created: 2024-02-11 Requires: Administrative privileges Environment Variables Used: - ChocoInstallScriptUrl - ChocoDownloadUrl - cChocoExChocoConfig - cChocoExSourcesConfig - cChocoExPackageConfig - cChocoExFeatureConfig - cChocoExBootStrapUri #> function Start-cChocoEx { [CmdletBinding()] param ( [Parameter()] [string] $SettingsURI, # Chocolatey Installation Directory [Parameter()] [string] $InstallDir = "$env:ProgramData\chocolatey", # Chocolatey Installation Script URL [Parameter()] [string] $ChocoInstallScriptUrl = 'https://chocolatey.org/install.ps1', # URL to chocolatey nupkg [Parameter()] [string] $ChocoDownloadUrl, # URL to cChoco sources configuration file [Parameter()] [string] $SourcesConfig, # URL to cCHoco packages [Parameter()] [array] $PackageConfig, # URL to cChoco Chocolatey configuration file [Parameter()] [string] $ChocoConfig, # URL to cChoco Chocolatey features configuration file [Parameter()] [string] $FeatureConfig, # Do not cache configuration files [Parameter()] [switch] $NoCache, # Wipe locally cached psd1 configurations [Parameter()] [switch] $WipeCache, # RandomDelay [Parameter()] [switch] $RandomDelay, # Loop the Function [Parameter()] [Switch] $Loop, # Loop Delay in Minutes [Parameter()] [int] $LoopDelay = 60, # Legacy Migration Automation [Parameter()] [Switch] $MigrateLegacyConfigurations, # OverrideMaintenanceWindow [Parameter()] [switch] $OverrideMaintenanceWindow, # Enable Desktop Notifications [Parameter()] [Switch] $EnableNotifications, # Set machine enviroment variables [Parameter()] [switch] $SetcChocoExEnvironment ) #Ensure Running as Administrator if (-Not (Test-IsAdmin)) { Write-Warning "This function requires elevated access, please reopen PowerShell as an Administrator" Break } #Enable TLS 1.2 #https://docs.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=net-5.0 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 #Set Notifications Variable $Global:EnableNotifications = $EnableNotifications #Default Maintenance Windows Active and Enabled $Global:MaintenanceWindowEnabled = $true $Global:MaintenanceWindowActive = $true #Validate Current Execution Policy $CurrentExecutionPolicy = Get-ExecutionPolicy try { $null = Set-ExecutionPolicy Bypass -Scope CurrentUser } catch { Write-Log -Severity 'Warning' -Message "Error Changing Execution Policy" } #Exclude Machines Set to Exclude Ring if ((Get-cChocoExRing) -eq 'Exclude') { Write-Log -Severity 'Information' -Message 'This machine is set to the Exclude Ring. cChocoEx Stopped' Break } #Ensure cChocoExBootStrapTask is not running $PendingFile = Join-Path $env:cChocoExDataFolder '.cChocoExPending' if (Test-Path -Path $PendingFile) { $PendingFileItem = Get-Item -Path $PendingFile $LastBootTime = (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime #Autoremove if older than 24 hours or older than last reboot if ($PendingFileItem.CreationTime -lt (Get-Date).AddDays(-1) -or $PendingFileItem.CreationTime -lt $LastBootTime) { Write-Log -Severity 'Warning' -Message 'Stale cChocoEx pending file found, removing' Remove-Item -Path $PendingFile -Force } } if (Test-Path -Path $PendingFile) { Write-Log -Severity 'Warning' -Message 'cChocoEx pending completion, please wait until it finishes to invoke again' break } Set-Content -Path $PendingFile -Value '' -Force #Ensure choco.exe is not active $i = 0 do { $IsChocoActive = Test-IsChocoActive if ($i -eq 1) { Write-Log -Severity 'Information' -Message 'Choco.exe is active, waiting up to 300 seconds' } if ($i -gt 0) { Start-Sleep -Seconds 1 } $i++ } until ( ($IsChocoActive -eq $False) -or ($i -gt 300) ) if (Test-IsChocoActive) { Write-Log -Severity 'Information' -Message 'Choco.exe is active, cChocoEx Stopped' Break } #Log Start try { Write-Log -Severity 'Information' -Message 'cChocoEx Started' Write-EventLog -LogName 'Application' -Source 'cChocoEx' -EventId 4000 -EntryType Information -Message 'cChocoEx Started' } catch { Write-Warning "Error Starting Log, wiping and retrying" Write-Log -Severity 'Information' -Message 'cChoco Bootstrap Started' -New Write-EventLog -LogName 'Application' -Source 'cChocoEx' -EventId 4000 -EntryType Information -Message 'cChocoEx Started' } #Register and Start cChocoEx Task Register-cChocoExTask #Update Media Folder $null = Copy-Item -Path (Join-Path -Path ($PSScriptRoot | Split-Path) -ChildPath 'Media\*') -Destination $cChocoExMediaFolder -Recurse -Force #Ensure cChoco Module Is Present and Available if (-not($ModuleBase)) { Write-Log -Severity 'Error' -Message 'Required Module cChoco Not Found' Break } #Migrate Legacy Configuration Files if ($MigrateLegacyConfigurations) { Move-LegacyConfigurations } #Evaluate Mainteance Window Override if ($OverrideMaintenanceWindow) { $Global:OverrideMaintenanceWindow = $True Write-Log -Severity 'Information' -Message 'Global OverrideMaintenanceWindow Enabled' } else { $Global:OverrideMaintenanceWindow = $False } #Ensure Notification Prerequisites are Installed and Imported if ($Global:EnableNotifications) { $OSMajorVersion = (Get-CimInstance -ClassName Win32_OperatingSystem -Property Version).Version.Split('.')[0] if ([int]$OSMajorVersion -lt 10) { Write-Log -Severity 'Warning' -Message 'Notifications Require Windows 10 or Server 2016 and Greater' $Global:EnableNotifications = $false } else { Install-BurntToast Install-RunAsUser } } ##################################### # Gather Environment Variables ##################################### #ChocoInstallScriptUrl if ($env:ChocoInstallScriptUrl) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:ChocoInstallScriptUrl: $env:ChocoInstallScriptUrl" $ChocoInstallScriptUrl = $env:ChocoInstallScriptUrl } #ChocoDownloadUrl if ($env:ChocoDownloadUrl -and ([string]::IsNullOrEmpty($ChocoDownloadUrl))) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:ChocoDownloadUrl: $env:ChocoDownloadUrl" $ChocoDownloadUrl = $env:ChocoDownloadUrl } #cChocoExChocoConfig if ($env:cChocoExChocoConfig -and ([string]::IsNullOrEmpty($ChocoConfig))) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:cChocoExChocoConfig: $env:cChocoExChocoConfig" $ChocoConfig = $env:cChocoExChocoConfig } #cChocoExSourcesConfig if ($env:cChocoExSourcesConfig -and ([string]::IsNullOrEmpty($SourcesConfig))) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:cChocoExSourcesConfig: $env:cChocoExSourcesConfig" $SourcesConfig = $env:cChocoExSourcesConfig } #cChocoExPackageConfig if ($env:cChocoExPackageConfig -and ([string]::IsNullOrEmpty($PackageConfig))) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:cChocoExPackageConfig: $env:cChocoExPackageConfig" $PackageConfig = $env:cChocoExPackageConfig -join ',' } #cChocoExFeatureConfig if ($env:cChocoExFeatureConfig -and ([string]::IsNullOrEmpty($FeatureConfig))) { Write-Log -Severity 'Information' -Message "Environment Variable `$env:cChocoExFeatureConfig: $env:cChocoExFeatureConfig" $FeatureConfig = $env:cChocoExFeatureConfig } ##################################### # Set Environment Variables ##################################### if ($SetcChocoExEnvironment) { #ChocoInstallScriptUrl if ($ChocoInstallScriptUrl) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:ChocoInstallScriptUrl: $ChocoInstallScriptUrl" [Environment]::SetEnvironmentVariable('ChocoInstallScriptUrl', $ChocoInstallScriptUrl, 'Machine') $env:ChocoInstallScriptUrl = $ChocoInstallScriptUrl } #ChocoDownloadUrl if ($ChocoDownloadUrl) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:ChocoDownloadUrl: $ChocoDownloadUrl" [Environment]::SetEnvironmentVariable('ChocoDownloadUrl', $ChocoDownloadUrl, 'Machine') $env:ChocoDownloadUrl = $ChocoDownloadUrl } #cChocoExChocoConfig if ($ChocoConfig) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:ChocoConfig: $ChocoConfig" [Environment]::SetEnvironmentVariable('cChocoExChocoConfig', $ChocoConfig, 'Machine') $env:cChocoExChocoConfig = $ChocoConfig } #cChocoExSourcesConfig if ($SourcesConfig) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:SourcesConfig: $SourcesConfig" [Environment]::SetEnvironmentVariable('cChocoExSourcesConfig', $SourcesConfig, 'Machine') $env:cChocoExSourcesConfig = $SourcesConfig } #cChocoExPackageConfig if ($PackageConfig) { $PackageConfigString = $PackageConfig -join ',' Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:cChocoExPackageConfig: $PackageConfigString" [Environment]::SetEnvironmentVariable('cChocoExPackageConfig', $PackageConfigString, 'Machine') $env:cChocoExPackageConfig = $PackageConfigString } #cChocoExFeatureConfig if ($FeatureConfig) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:FeatureConfig: $FeatureConfig" [Environment]::SetEnvironmentVariable('cChocoExFeatureConfig', $FeatureConfig, 'Machine') $env:cChocoExFeatureConfig = $FeatureConfig } #cChocoExBootStrapUri if ($env:cChocoExBootStrapUri) { Write-Log -Severity 'Information' -Message "Setting Environment Variable `$env:cChocoExBootStrapUri: $env:cChocoExBootStrapUri" [Environment]::SetEnvironmentVariable('cChocoExBootStrapUri', $env:cChocoExBootStrapUri, 'Machine') } } ##################################### # cChocoInstaller ##################################### $Configuration = @{ InstallDir = $InstallDir ChocoInstallScriptUrl = $ChocoInstallScriptUrl } #Set Enviromental Variable for chocolatey url to nupkg if ($ChocoDownloadUrl) { $env:chocolateyDownloadUrl = $ChocoDownloadUrl } Start-cChocoInstaller -Configuration $Configuration #Ensure Chocolatey Config is Valid #https://github.com/chocolatey/choco/issues/1047 if (-not(Test-ChocolateyConfig)) { $Reset = Reset-ChocolateyConfig Write-Log -Severity 'Information' -Message $Reset.Config Write-Log -Severity 'Information' -Message "Chocolatey Config Reset: $($Reset.Reset)" } #Evaluate Random Delay Switch if ($RandomDelay) { $RandomSeconds = Get-Random -Minimum 0 -Maximum 1800 Write-Log -Severity 'Information' -Message "Random Delay Enabled" Write-Log -Severity 'Information' -Message "Delay: $RandomSeconds`s" Start-Sleep -Seconds $RandomSeconds } #Settings if ($SettingsURI) { $Destination = (Join-Path $cChocoExTMPConfigurationFolder "bootstrap-cchoco.psd1") try { Write-Log -Severity 'Information' -Message "Downloading SettingsURI File" Write-Log -Severity 'Information' -Message "Source: $SettingsURI" Write-Log -Severity 'Information' -Message "Destination: $Destination" switch (Test-PathEx -Path $SettingsURI) { 'URL' { Invoke-WebRequest -Uri $SettingsURI -UseBasicParsing -OutFile $Destination } 'FileSystem' { Copy-Item -Path $SettingsURI -Destination $Destination -Force } } } catch { Write-Log -Severity 'Warning' -Message $_.Exception.Message } $SettingsFile = Import-PowerShellDataFile -Path $Destination $Settings = $SettingsFile | ForEach-Object { $_.Keys | ForEach-Object { $SettingsFile.$_ } } #Variables $InstallDir = $Settings.InstallDir $ChocoInstallScriptUrl = $Settings.ChocoInstallScriptUrl $SourcesConfig = $Settings.SourcesConfig $PackageConfig = $Settings.PackageConfig $ChocoConfig = $Settings.ChocoConfig $FeatureConfig = $Settings.FeatureConfig } Write-Log -Severity 'Information' -Message "cChocoEx Settings" Write-Log -Severity 'Information' -Message "SettingsURI: $SettingsURI" Write-Log -Severity 'Information' -Message "InstallDir: $InstallDir" Write-Log -Severity 'Information' -Message "ChocoInstallScriptUrl: $ChocoInstallScriptUrl" Write-Log -Severity 'Information' -Message "SourcesConfig: $SourcesConfig" Write-Log -Severity 'Information' -Message "PackageConfig: $PackageConfig" Write-Log -Severity 'Information' -Message "ChocoConfig: $ChocoConfig" Write-Log -Severity 'Information' -Message "FeatureConfig: $FeatureConfig" if ($WipeCache) { Write-Log -Severity 'Information' -Message 'WipeCache Enabled. Wiping any previously downloaded psd1 configuration files' Get-ChildItem -Path $cChocoExConfigurationFolder -Filter *.psd1 | Remove-Item -Recurse -Force } #Preclear any previously downloaded NoCache configuration files if ($NoCache) { Write-Log -Severity 'Information' -Message 'NoCache Enabled. Wiping any previously downloaded NoCache configuration files from temp' Get-ChildItem -Path $cChocoExTMPConfigurationFolder -Filter *.psd1 | Remove-Item -Recurse -Force } #Copy Config Config? $Global:ChocoConfigDestination = (Join-Path $cChocoExConfigurationFolder "config.psd1") if ($ChocoConfig) { if ($NoCache) { $Global:ChocoConfigDestination = (Join-Path $cChocoExTMPConfigurationFolder "config.psd1") } try { Write-Log -Severity 'Information' -Message "Downloading Choco Config File" Write-Log -Severity 'Information' -Message "Source: $ChocoConfig" Write-Log -Severity 'Information' -Message "Destination: $ChocoConfigDestination" switch (Test-PathEx -Path $ChocoConfig) { 'URL' { Invoke-WebRequest -Uri $ChocoConfig -UseBasicParsing -OutFile $ChocoConfigDestination } 'FileSystem' { Copy-Item -Path $ChocoConfig -Destination $ChocoConfigDestination -Force } } Write-Log -Severity 'Information' -Message 'Chocolatey Config File Set.' } catch { Write-Log -Severity 'Warning' -Message $_.Exception.Message } } #Copy Sources Config $Global:SourcesConfigDestination = (Join-Path $cChocoExConfigurationFolder "sources.psd1") if ($SourcesConfig) { if ($NoCache) { $Global:SourcesConfigDestination = (Join-Path $cChocoExTMPConfigurationFolder "sources.psd1") } try { Write-Log -Severity 'Information' -Message "Downloading Source Config File" Write-Log -Severity 'Information' -Message "Source: $SourcesConfig" Write-Log -Severity 'Information' -Message "Destination: $SourcesConfigDestination" switch (Test-PathEx -Path $SourcesConfig) { 'URL' { Invoke-WebRequest -Uri $SourcesConfig -UseBasicParsing -OutFile $SourcesConfigDestination } 'FileSystem' { Copy-Item -Path $SourcesConfig -Destination $SourcesConfigDestination -Force } } Write-Log -Severity 'Information' -Message 'Chocolatey Sources File Set.' } catch { Write-Log -Severity 'Warning' -Message $_.Exception.Message } } #Copy Features Config $Global:FeatureConfigDestination = (Join-Path $cChocoExConfigurationFolder "features.psd1") if ($FeatureConfig) { if ($NoCache) { $Global:FeatureConfigDestination = (Join-Path $cChocoExTMPConfigurationFolder "features.psd1") } try { Write-Log -Severity 'Information' -Message "Downloading Feature Config File" Write-Log -Severity 'Information' -Message "Source: $FeatureConfig" Write-Log -Severity 'Information' -Message "Destination: $FeatureConfigDestination" switch (Test-PathEx -Path $FeatureConfig) { 'URL' { Invoke-WebRequest -Uri $FeatureConfig -UseBasicParsing -OutFile $FeatureConfigDestination } 'FileSystem' { Copy-Item -Path $FeatureConfig -Destination $FeatureConfigDestination -Force } } Write-Log -Severity 'Information' -Message 'Chocolatey Feature File Set.' } catch { Write-Log -Severity 'Warning' -Message $_.Exception.Message } } #Copy Package Config $Global:PackageConfigDestination = $cChocoExConfigurationFolder if ($PackageConfig) { if ($NoCache) { $Global:PackageConfigDestination = $cChocoExTMPConfigurationFolder } $PackageConfig | ForEach-Object { $Path = $_ $Destination = (Join-Path $PackageConfigDestination ($_ | Split-Path -Leaf)) try { Write-Log -Severity 'Information' -Message "Downloading Package Config File" Write-Log -Severity 'Information' -Message "Source: $Path" Write-Log -Severity 'Information' -Message "Destination: $Destination" switch (Test-PathEx -Path $_) { 'URL' { Invoke-WebRequest -Uri $Path -UseBasicParsing -OutFile $Destination } 'FileSystem' { Copy-Item -Path $Path -Destination $Destination -Force } } Write-Log -Severity 'Information' -Message 'Chocolatey Package File Set.' } catch { Write-Log -Severity 'Warning' -Message $_.Exception.Message } } } ##################################### # cChocoConfig ##################################### if (Test-Path $ChocoConfigDestination ) { $ConfigImport = $null $ConfigImport = Import-PowerShellDataFile $ChocoConfigDestination Start-cChocoConfig -ConfigImport $ConfigImport } else { Write-Log -Severity 'Information' -Message "File not found, configuration will not be modified" } ##################################### # cChocoFeature ##################################### if (Test-Path $FeatureConfigDestination ) { $ConfigImport = $null $ConfigImport = Import-PowerShellDataFile $FeatureConfigDestination Start-cChocoFeature -ConfigImport $ConfigImport } else { Write-Log -Severity 'Information' -Message "File not found, features will not be modified" } ##################################### # cChocoSource ##################################### if (Test-Path $SourcesConfigDestination ) { $ConfigImport = $null $ConfigImport = Import-PowerShellDataFile $SourcesConfigDestination Start-cChocoSource -ConfigImport $ConfigImport } else { Write-Log -Severity 'Information' -Message "File not found, sources will not be modified" } ##################################### # cChocoPackageInstall ##################################### [array]$Configurations = $null Get-ChildItem -Path $PackageConfigDestination -Filter *.psd1 | Where-Object { $_.Name -notmatch "sources.psd1|config.psd1|features.psd1" } | ForEach-Object { $ConfigImport = $null $ConfigImport = Import-PowerShellDataFile $_.FullName $Configurations += $ConfigImport | ForEach-Object { $_.Keys | ForEach-Object { $ConfigImport.$_ } } } if ($Configurations ) { Start-cChocoPackageInstall -Configurations $Configurations } else { Write-Log -Severity 'Information' -Message "File not found, packages will not be modified" } #Cleanup #Preclear any previously downloaded NoCache configuration files if ($NoCache) { Write-Log -Severity "Information" -Message "Preclear any previously downloaded NoCache configuration files" Get-ChildItem -Path $cChocoExTMPConfigurationFolder -Filter *.psd1 | Remove-Item -Recurse -Force } #Register cChocoEx BootStrap Task if Enabled if ($Loop) { Write-Log -Severity "Information" -Message "Function Looping Enabled" Write-Log -Severity "Information" -Message "Looping Delay: $LoopDelay Minutes" Register-cChocoExBootStrapTask -LoopDelay $LoopDelay } #Clear Pending file if (Test-Path -Path $PendingFile) { Remove-Item -Path $PendingFile -Force } $null = Set-ExecutionPolicy $CurrentExecutionPolicy -Scope CurrentUser -ErrorAction SilentlyContinue Write-EventLog -LogName 'Application' -Source 'cChocoEx' -EventId 4001 -EntryType Information -Message 'cChocoEx Finished' RotateLog } |