From 92a39fb9d1a094bcdb0a48be30db81e9cc02f64d Mon Sep 17 00:00:00 2001 From: RedEchidnaUK Date: Tue, 7 Mar 2023 15:17:40 +0000 Subject: [PATCH] Added AZBlob for Mods --- Policies/WAU.admx | 12 ++++- Policies/en-US/WAU.adml | 22 ++++++-- README.md | 7 ++- Winget-AutoUpdate-Install.ps1 | 13 +++-- Winget-AutoUpdate/Winget-Upgrade.ps1 | 4 +- Winget-AutoUpdate/functions/Get-AZCopy.ps1 | 50 +++++++++++++++++++ Winget-AutoUpdate/functions/Get-Policies.ps1 | 8 +++ Winget-AutoUpdate/functions/Test-ModsPath.ps1 | 41 ++++++++++++++- 8 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 Winget-AutoUpdate/functions/Get-AZCopy.ps1 diff --git a/Policies/WAU.admx b/Policies/WAU.admx index 146524e..3106d8b 100644 --- a/Policies/WAU.admx +++ b/Policies/WAU.admx @@ -1,12 +1,13 @@ - + - + + @@ -99,6 +100,13 @@ + + + + + + + diff --git a/Policies/en-US/WAU.adml b/Policies/en-US/WAU.adml index c9545de..e9d1f05 100644 --- a/Policies/en-US/WAU.adml +++ b/Policies/en-US/WAU.adml @@ -1,11 +1,12 @@ - + WinGet-AutoUpdate WinGet-AutoUpdate GPO Management Winget-AutoUpdate Winget-AutoUpdate version 1.16.0 or later + Winget-AutoUpdate version 1.16.5 or later Activate WAU GPO Management This policy setting is an overriding toggle for GPO Management of Winget-AutoUpdate. Bypass Black/White list for User @@ -43,10 +44,16 @@ If this policy is disabled or not configured, the default is No. If "Application GPO Blacklist/Whitelist" is set in this GPO the Path can be: GPO If this policy is disabled or not configured, the default ListPath is used (WAU InstallLocation). - Get Mods from external Path (URL/UNC/Local) - If this policy is enabled, you can set a (URL/UNC/Local) Path to external mods other than the default. + Get Mods from external Path (URL/UNC/Local/AzureBlob) + If this policy is enabled, you can set a (URL/UNC/Local/AzureBlob) Path to external mods other than the default. -If this policy is disabled or not configured, the default ModsPath is used (WAU InstallLocation). +If this policy is disabled or not configured, the default ModsPath is used (WAU InstallLocation). + +Note: When set to 'AzureBlob', ensure you also configure 'Set Azure Blob URL with SAS token'. + Set Azure Blob URL with SAS Token + If this policy is enabled, you can set an Azure Storage Blob URL with SAS token for use with the 'Mods' feature. The URL must include the SAS token and have 'read' and 'list' permissions. + +If this policy is disabled or not configured, the value is blank and Azure Blob storage will NOT work. Notification Level If this policy is enabled, you can configure the Notification Level: 1. Full (Default) @@ -148,8 +155,13 @@ If this policy is disabled or not configured, the default size is used. - + + + + + + diff --git a/README.md b/README.md index 2645be7..fabd11c 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ If `-ListPath` is set to **GPO** the Black/White List can be managed from within Thanks to [Weatherlights](https://github.com/Weatherlights) in [#256 (reply in thread)](https://github.com/Romanitho/Winget-AutoUpdate/discussions/256#discussioncomment-4710599)! **-ModsPath** -Get Mods from external Path (**URL/UNC/Local**) - download/copy to `mods` in Winget-AutoUpdate installation location if external mods are newer. +Get Mods from external Path (**URL/UNC/Local/AzureBlob**) - download/copy to `mods` in Winget-AutoUpdate installation location if external mods are newer. For **URL**: This requires a site directory with **Directory Listing Enabled** and no index page overriding the listing of files (or an index page with href listing of all the **Mods** to be downloaded): ``` @@ -121,6 +121,11 @@ Validated on **IIS/Apache**. - The extension **.ps1** must be added as **MIME Types** (text/powershell-script) otherwise it's displayed in the listing but can't be opened - Files with special characters in the filename can't be opened by default from an IIS server - config must be administrated: **Enable Allow double escaping** in '**Request Filtering**' +For **AzureBlob**: This requires the parameter **-AzureBlobURL** to be set with an appropriate Azure Blob Storage URL including the SAS token. See **-AzureBlobURL** for more information. + +**-AzureBlobURL** +Used in conjunction with the **-ModsPath** parameter to provide the Azure Storage Blob URL with SAS token. The SAS token must, at a minimum, have 'Read' and 'List' permissions. It is recommended to set the permisions at the container level and rotate the SAS token on a regular basis. Ensure the container reflects the same structure as found under the initial `mods` folder. (From version 1.16.4). + **-InstallUserContext** Install WAU with system and **user** context executions (From version 1.15.3). diff --git a/Winget-AutoUpdate-Install.ps1 b/Winget-AutoUpdate-Install.ps1 index c7a5a0a..741b439 100644 --- a/Winget-AutoUpdate-Install.ps1 +++ b/Winget-AutoUpdate-Install.ps1 @@ -29,10 +29,13 @@ Disable Winget-AutoUpdate update checking. By default, WAU auto update if new ve Use White List instead of Black List. This setting will not create the "exclude_apps.txt" but "include_apps.txt" .PARAMETER ListPath -Get Black/White List from Path (URL/UNC/Local) +Get Black/White List from Path (URL/UNC/GPO/Local) .PARAMETER ModsPath -Get mods from Path (URL/UNC/Local) +Get mods from Path (URL/UNC/Local/AzureBlob) + +.PARAMETER AzureBlobURL +Set the Azure Storage Blob URL including the SAS token. The token requires at a minimum 'Read' and 'List' permissions. It is recommended to set this at the container level .PARAMETER Uninstall Remove scheduled tasks and scripts. @@ -93,6 +96,7 @@ param( [Parameter(Mandatory = $False)] [Alias('Path')] [String] $WingetUpdatePath = "$env:ProgramData\Winget-AutoUpdate", [Parameter(Mandatory = $False)] [Alias('List')] [String] $ListPath, [Parameter(Mandatory = $False)] [Alias('Mods')] [String] $ModsPath, + [Parameter(Mandatory = $False)] [Alias('AzureBlobURL')] [String] $AzureBlobSASURL, [Parameter(Mandatory = $False)] [Switch] $DoNotUpdate = $false, [Parameter(Mandatory = $False)] [Switch] $DisableWAUAutoUpdate = $false, [Parameter(Mandatory = $False)] [Switch] $RunOnMetered = $false, @@ -113,7 +117,7 @@ param( <# APP INFO #> -$WAUVersion = "1.16.4" +$WAUVersion = "1.16.5" <# FUNCTIONS #> @@ -363,6 +367,9 @@ function Install-WingetAutoUpdate { if ($ModsPath) { New-ItemProperty $regPath -Name WAU_ModsPath -Value $ModsPath -Force | Out-Null } + if ($AzureBlobSASURL) { + New-ItemProperty $regPath -Name WAU_AzureBlobSASURL -Value $AzureBlobSASURL -Force | Out-Null + } if ($BypassListForUsers) { New-ItemProperty $regPath -Name WAU_BypassListForUsers -Value 1 -PropertyType DWord -Force | Out-Null } diff --git a/Winget-AutoUpdate/Winget-Upgrade.ps1 b/Winget-AutoUpdate/Winget-Upgrade.ps1 index 6f78139..c0d2fd5 100644 --- a/Winget-AutoUpdate/Winget-Upgrade.ps1 +++ b/Winget-AutoUpdate/Winget-Upgrade.ps1 @@ -87,7 +87,7 @@ if (Test-Network) { $WAUDisableAutoUpdate = $WAUConfig.WAU_DisableAutoUpdate #If yes then check WAU update if run as System if ($WAUDisableAutoUpdate -eq 1) { - Write-Log "WAU AutoUpdate is Disabled." "Grey" + Write-Log "WAU AutoUpdate is Disabled." "Gray" } else { Write-Log "WAU AutoUpdate is Enabled." "Green" @@ -152,7 +152,7 @@ if (Test-Network) { if ($WAUConfig.WAU_ModsPath) { $ModsPathClean = $($WAUConfig.WAU_ModsPath.TrimEnd(" ", "\", "/")) Write-Log "WAU uses External Mods from: $ModsPathClean" - $NewMods, $DeletedMods = Test-ModsPath $ModsPathClean $WAUConfig.InstallLocation.TrimEnd(" ", "\") + $NewMods, $DeletedMods = Test-ModsPath $ModsPathClean $WAUConfig.InstallLocation.TrimEnd(" ", "\") $WAUConfig.WAU_AzureBlobSASURL.TrimEnd(" ") if ($ReachNoPath) { Write-Log "Couldn't reach/find/compare/copy from $ModsPathClean..." "Red" $Script:ReachNoPath = $False diff --git a/Winget-AutoUpdate/functions/Get-AZCopy.ps1 b/Winget-AutoUpdate/functions/Get-AZCopy.ps1 new file mode 100644 index 0000000..bfe7994 --- /dev/null +++ b/Winget-AutoUpdate/functions/Get-AZCopy.ps1 @@ -0,0 +1,50 @@ +#Function to get AZCopy if it doesn't exist and update it if it does + +Function Get-AZCopy ($WingetUpdatePath){ + + $AZCopyLink = (Invoke-WebRequest -Uri https://aka.ms/downloadazcopy-v10-windows -MaximumRedirection 0 -ErrorAction SilentlyContinue).headers.location + $AZCopyVersionRegex = [regex]::new("(\d+\.\d+\.\d+)") + $AZCopyLatestVersion = $AZCopyVersionRegex.Match($AZCopyLink).Value + + if ($null -eq $AZCopyLatestVersion -or "" -eq $AZCopyLatestVersion) { + $AZCopyLatestVersion = "0.0.0" + } + + if (Test-Path -Path "$WingetUpdatePath\azcopy.exe" -PathType Leaf) { + $AZCopyCurrentVersion = & "$WingetUpdatePath\azcopy.exe" -v + $AZCopyCurrentVersion = $AZCopyVersionRegex.Match($AZCopyCurrentVersion).Value + Write-Log "AZCopy version $AZCopyCurrentVersion found" + } + else { + Write-Log "AZCopy not already installed" + $AZCopyCurrentVersion = "0.0.0" + } + + if (([version] $AZCopyCurrentVersion) -lt ([version] $AZCopyLatestVersion)) { + Write-Log "Installing version $AZCopyLatestVersion of AZCopy" + Invoke-WebRequest -Uri $AZCopyLink -OutFile "$WingetUpdatePath\azcopyv10.zip" + Write-Log "Extracting AZCopy zip file" + + Expand-archive -Path "$WingetUpdatePath\azcopyv10.zip" -Destinationpath "$WingetUpdatePath" -Force + + $AZCopyPathSearch = Resolve-Path -path "$WingetUpdatePath\azcopy_*" + + if ($AZCopyPathSearch -is [array]) { + $AZCopyEXEPath = $AZCopyPathSearch[$AZCopyPathSearch.Length - 1] + } + else { + $AZCopyEXEPath = $AZCopyPathSearch + } + + Write-Log "Copying 'azcopy.exe' to main folder" + Copy-Item "$AZCopyEXEPath\azcopy.exe" -Destination "$WingetUpdatePath\" + + Write-Log "Removing temporary AZCopy files" + Remove-Item -Path $AZCopyEXEPath -Recurse + Remove-Item -Path "$WingetUpdatePath\azcopyv10.zip" + + $AZCopyCurrentVersion = & "$WingetUpdatePath\azcopy.exe" -v + $AZCopyCurrentVersion = $AZCopyVersionRegex.Match($AZCopyCurrentVersion).Value + Write-Log "AZCopy version $AZCopyCurrentVersion installed" + } +} \ No newline at end of file diff --git a/Winget-AutoUpdate/functions/Get-Policies.ps1 b/Winget-AutoUpdate/functions/Get-Policies.ps1 index 5e4491f..bad0ee1 100644 --- a/Winget-AutoUpdate/functions/Get-Policies.ps1 +++ b/Winget-AutoUpdate/functions/Get-Policies.ps1 @@ -69,6 +69,14 @@ Function Get-Policies { Remove-ItemProperty $regPath -Name WAU_ModsPath -Force -ErrorAction SilentlyContinue | Out-Null $ChangedSettings++ } + if ($null -ne $($WAUPolicies.WAU_AzureBlobSASURL) -and ($($WAUPolicies.WAU_AzureBlobSASURL) -ne $($WAUConfig.WAU_AzureBlobSASURL))) { + New-ItemProperty $regPath -Name WAU_AzureBlobSASURL -Value $($WAUPolicies.WAU_AzureBlobSASURL.TrimEnd(" ", "\", "/")) -Force | Out-Null + $ChangedSettings++ + } + elseif ($null -eq $($WAUPolicies.WAU_AzureBlobSASURL) -and $($WAUConfig.WAU_AzureBlobSASURL)) { + Remove-ItemProperty $regPath -Name WAU_AzureBlobSASURL -Force -ErrorAction SilentlyContinue | Out-Null + $ChangedSettings++ + } if ($null -ne $($WAUPolicies.WAU_NotificationLevel) -and ($($WAUPolicies.WAU_NotificationLevel) -ne $($WAUConfig.WAU_NotificationLevel))) { New-ItemProperty $regPath -Name WAU_NotificationLevel -Value $($WAUPolicies.WAU_NotificationLevel) -Force | Out-Null diff --git a/Winget-AutoUpdate/functions/Test-ModsPath.ps1 b/Winget-AutoUpdate/functions/Test-ModsPath.ps1 index 29f0bdf..e9deb5e 100644 --- a/Winget-AutoUpdate/functions/Test-ModsPath.ps1 +++ b/Winget-AutoUpdate/functions/Test-ModsPath.ps1 @@ -1,6 +1,6 @@ #Function to check Mods External Path -function Test-ModsPath ($ModsPath, $WingetUpdatePath) { +function Test-ModsPath ($ModsPath, $WingetUpdatePath, $AzureBlobSASURL) { # URL, UNC or Local Path # Get local and external Mods paths $LocalMods = -join ($WingetUpdatePath, "\", "mods") @@ -134,6 +134,45 @@ function Test-ModsPath ($ModsPath, $WingetUpdatePath) { } return $ModsUpdated, $DeletedMods } + # If Path is Azure Blob + elseif ($ExternalMods -like "AzureBlob") { + Write-Log "Azure Blob Storage set as mod source" + Write-Log "Checking AZCopy" + Get-AZCopy $WingetUpdatePath + #Safety check to make sure we really do have azcopy.exe and a Blob URL + if ((Test-Path -Path "$WingetUpdatePath\azcopy.exe" -PathType Leaf) -and ($null -ne $AzureBlobSASURL)) { + Write-Log "Syncing Blob storage with local storage" + + $AZCopySyncOutput = & $WingetUpdatePath\azcopy.exe sync "$AzureBlobSASURL" "$LocalMods" --from-to BlobLocal --delete-destination=true + $AZCopyOutputLines = $AZCopySyncOutput.Split([Environment]::NewLine) + + foreach( $_ in $AZCopyOutputLines){ + $AZCopySyncAdditionsRegex = [regex]::new("(?<=Number of Copy Transfers Completed:\s+)\d+") + $AZCopySyncDeletionsRegex = [regex]::new("(?<=Number of Deletions at Destination:\s+)\d+") + $AZCopySyncErrorRegex = [regex]::new("^Cannot perform sync due to error:") + + $AZCopyAdditions = [int] $AZCopySyncAdditionsRegex.Match($_).Value + $AZCopyDeletions = [int] $AZCopySyncDeletionsRegex.Match($_).Value + + if ($AZCopyAdditions -ne 0){ + $ModsUpdated = $AZCopyAdditions + } + + if ($AZCopyDeletions -ne 0){ + $DeletedMods = $AZCopyDeletions + } + + if ($AZCopySyncErrorRegex.Match($_).Value){ + Write-Log "AZCopy Sync Error! $_" + } + } + } + else { + Write-Log "Error 'azcopy.exe' or SAS Token not found!" + } + + return $ModsUpdated, $DeletedMods + } # If path is UNC or local else { $ExternalBins = "$ModsPath\bins"