From 43bffc3403301fa8bf4f7e7ec52b823d6cea63a6 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:08:09 -0800 Subject: [PATCH 01/16] Clean up and rewrite --- .../doc/PowerShell/Get-WinGetManifest.md | 2 +- Tools/PowershellModule/doc/README.md | 6 +- ...st-source-azure.md => WingetRestSource.md} | 0 ...sts.ps1 => Test-PowerShellModuleExist.ps1} | 13 +-- .../src/Microsoft.WinGet.Source.psd1 | 107 +++--------------- .../src/Microsoft.WinGet.Source.psm1 | 58 +++++----- src/WinGet.RestSource.sln | 2 +- 7 files changed, 48 insertions(+), 140 deletions(-) rename Tools/PowershellModule/doc/{new-winget-rest-source-azure.md => WingetRestSource.md} (100%) rename Tools/PowershellModule/src/Library/{Test-PowerShellModuleExists.ps1 => Test-PowerShellModuleExist.ps1} (80%) diff --git a/Tools/PowershellModule/doc/PowerShell/Get-WinGetManifest.md b/Tools/PowershellModule/doc/PowerShell/Get-WinGetManifest.md index 401da20d..73d6de00 100644 --- a/Tools/PowershellModule/doc/PowerShell/Get-WinGetManifest.md +++ b/Tools/PowershellModule/doc/PowerShell/Get-WinGetManifest.md @@ -9,7 +9,7 @@ schema: 2.0.0 ## SYNOPSIS Connects to the specified source REST API, or local file system path to retrieve the package Manifests, returning an array of all Manifests found. -Allows for filtering results based on the name when targetting the REST APIs. +Allows for filtering results based on the name when targeting the REST APIs. ## SYNTAX diff --git a/Tools/PowershellModule/doc/README.md b/Tools/PowershellModule/doc/README.md index 21d1785a..9b587752 100644 --- a/Tools/PowershellModule/doc/README.md +++ b/Tools/PowershellModule/doc/README.md @@ -1,10 +1,10 @@ # Create a Windows Package Manager REST source -This section provides guidance on how to create a REST source for the Windows Package Manager. ISVs or Publishers may host and manage a rest source if they would like full control of the applications available in a source. An independently hosted source may choose to expose the read endpoints publicly or restrict access to specific IP address via the addition of a traffic shaping module. The basic setups configured by the cmdlets and examples result in a source that is publicly readable but requires an authorization key to manage. +This section provides guidance on how to create a REST source for the Windows Package Manager. ISVs or Publishers may host and manage a rest source if they would like full control of the applications available in a source. An independently hosted source may choose to expose the read endpoints publicly or restrict access to specific IP address via the addition of a traffic shaping module. The basic setups configured by the cmdlets and examples result in a source that is publicly readable but requires an authorization key to manage. Windows Package Manager offers a comprehensive package manager solution including a command line tool and a set of services for installing applications. For more general package submission information, see [submit packages to Windows Package Manager](https://learn.microsoft.com/windows/package-manager/package/). There are two ways available for managing REST source repositories with Windows Package Manager: -- [Manage Windows Package Manager REST source with PowerShell](new-winget-rest-source-azure.md#manage-windows-package-manager-rest-source-with-powershell) -- [Manage Windows Package Manager REST source manually](new-winget-rest-source-azure.md#manage-windows-package-manager-rest-source-manually) \ No newline at end of file +- [Manage Windows Package Manager REST source with PowerShell](WingetRestSource.md#manage-windows-package-manager-rest-source-with-powershell) +- [Manage Windows Package Manager REST source manually](WingetRestSource.md#manage-windows-package-manager-rest-source-manually) \ No newline at end of file diff --git a/Tools/PowershellModule/doc/new-winget-rest-source-azure.md b/Tools/PowershellModule/doc/WingetRestSource.md similarity index 100% rename from Tools/PowershellModule/doc/new-winget-rest-source-azure.md rename to Tools/PowershellModule/doc/WingetRestSource.md diff --git a/Tools/PowershellModule/src/Library/Test-PowerShellModuleExists.ps1 b/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 similarity index 80% rename from Tools/PowershellModule/src/Library/Test-PowerShellModuleExists.ps1 rename to Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 index 55000817..343380c4 100644 --- a/Tools/PowershellModule/src/Library/Test-PowerShellModuleExists.ps1 +++ b/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 @@ -52,21 +52,12 @@ Function Test-PowerShellModuleExist ## Specifies that a module is missing if(!($Result)) { $ValidationStatus = $false + Write-Error "Missing required PowerShell modules. Run the following command to install the missing modules: Install-Module $RequiredModule" } } - - if(!($ValidationStatus)) { - ## Module Validation failed - $ErrorMessage = "Missing required PowerShell modules. Run the following command to install the missing modules: Install-Module Az" - $ErrReturnObject = @{ - TestResults = $TestResult - } - - Write-Error -Message $ErrorMessage -Category NotInstalled -TargetObject $ErrReturnObject - } } "Single" { - ## Determines if the PowerShell Module is missing, If missing false if missing + ## Determines if the PowerShell Module is installed if(!$(Get-Module -ListAvailable -Name $RequiredModule) ) { $ValidationStatus = $false } diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index a693b843..2b0f194f 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -3,132 +3,53 @@ # # Module manifest for module 'Microsoft.WinGet.Source' # -# Generated by: romaclac -# -# Generated on: 09/06/2023 -# @{ - # Script module or binary module file associated with this manifest. RootModule = 'Microsoft.WinGet.Source.psm1' - + # Version number of this module. ModuleVersion = '0.1.0' - - # Supported PSEditions - # CompatiblePSEditions = @() - + # ID used to uniquely identify this module GUID = 'b70c845d-ddb1-4454-bfc2-a874783c2d04' - - # Author of this module - # Author = '' - + # Company or vendor of this module CompanyName = 'Microsoft' - + # Copyright statement for this module Copyright = '(c) Microsoft. All rights reserved.' - + # Description of the functionality provided by this module Description = 'This module provides support for working with Windows Package Manager REST based sources.' - + # Minimum version of the PowerShell engine required by this module PowerShellVersion = '5.1' - - # Name of the PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '' - - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # ClrVersion = '' - - # Processor architecture (None, X86, Amd64) required by this module - # ProcessorArchitecture = '' - - # Modules that must be imported into the global environment prior to importing this module - # RequiredModules = @() - - # Assemblies that must be loaded prior to importing this module - # RequiredAssemblies = @() - - # Script files (.ps1) that are run in the caller's environment prior to importing this module. - # ScriptsToProcess = @() - - # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() - - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() - - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() - + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Convert-YamlToJson") - + # Cmdlets to export from this module, for best performance, do not use wild cards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() # Variables to export from this module VariablesToExport = '*' - + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. AliasesToExport = @() - - # DSC resources to export from this module - # DscResourcesToExport = @() - - # List of all modules packaged with this module - # ModuleList = @() - - # List of all files packaged with this module - # FileList = @() - + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ - PSData = @{ - # Tags applied to this module. These help with module discovery in online galleries. Tags = @("winget") - - # A URL to the license for this module. - # LicenseUri = '' - + # A URL to the main website for this project. ProjectUri = 'https://github.com/microsoft/winget-cli-restsource' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - # ReleaseNotes = '' - + PreRelease = 'alpha' - - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false - - # External dependent modules of this module - # ExternalModuleDependencies = @() - - } # End of PSData hashtable - - } # End of PrivateData hashtable - - # HelpInfo URI of this module - # HelpInfoURI = '' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' - + } } +} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index 77e8f92a..9a9948de 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -1,50 +1,46 @@ -<# - Importing the Module requires that the Script be signed... or that Running scripts be approved for the computer. - - Compiled RestAPIs Compressed folder must exist in the ".\Library\RestAPI\" container. - Desktop AppInstaller Library must exist in the ".\Library\AppInstallerLib" container. -#> +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# +# Module script for module 'Microsoft.WinGet.Source' +# ## Loads Libraries Get-ChildItem -Path "$PSScriptRoot\Library" -Filter *.ps1 | foreach-object { . $_.FullName } [string] $WinGetPSEdition = "Core" +[string] $WinGetPSVersion = "7.4.0" ## Loads the binaries from the Desktop App Installer Library - Only if running PowerShell at a specified edition -if ($PSEdition -eq $WinGetPSEdition) { - try { - Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" - $WinGetDesktopAppInstallerLibLoaded=$true - } - catch [System.Reflection.ReflectionTypeLoadException] { - Write-Host "Message: $($_.Exception.Message)" - Write-Host "StackTrace: $($_.Exception.StackTrace)" - Write-Host "LoaderExceptions: $($_.Exception.LoaderExceptions)" - throw $_ - } +if ($PSEdition -eq $WinGetPSEdition && [System.Version]$PSVersion -ge [System.Version]$WinGetPSVersion) { + try { + Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" + } + catch { + ## Exceptions thrown by Add-Type will not fail the Import-Module. Catch and re-throw to fail the Import-Module. + throw $_ + } } else { - throw "Unable to load required binaries. Verify you are using Powershell $($WinGetPSEdition)." + throw "Unable to load required binaries. Verify you are using Powershell $WinGetPSEdition or later." } ## Validates that the required Azure Modules are present when the script is imported. -[string[]]$RequiredModules = @("Az.Resources", "Az.Accounts", "Az.Websites", "Az.Functions", "powershell-yaml") +[string[]]$RequiredModules = @("Az", "powershell-yaml") -## Verifies that the Azure Modules were previously installed. -[Boolean] $TestResult = Test-PowerShellModuleExist -Modules $RequiredModules -if (!$TestResult) { - ## Installs the required Azure Modules if not already installed - $RequiredModules | ForEach-Object { Install-Module $_ -Force } +foreach ($RequiredModule in $RequiredModules) { + ## Tests if the module is installed + $Result = Test-PowerShellModuleExist -Name $RequiredModule + + ## Install the missing module + if(!($Result)) { + Install-Module $RequiredModule -Force -AllowClobber + } } ## Verifies that the Azure Modules were successfully installed. [Boolean] $TestResult = Test-PowerShellModuleExist -Modules $RequiredModules if (!$TestResult) { - ## Modules have been identified as missing - Write-Host "" - $ErrorMessage = "There are missing PowerShell modules that must be installed.`n" - $ErrorMessage += " Some or all PowerShell functions included in this library will fail.`n" - $ErrorMessage += " Run the following command to install the missing modules: Install-Module Az -Force`n`n" - - Write-Error -Message $ErrorMessage + ## Modules have been identified as missing + $ErrorMessage = "There are missing PowerShell modules that must be installed. Some or all PowerShell functions included in this library will fail." + throw $ErrorMessage } \ No newline at end of file diff --git a/src/WinGet.RestSource.sln b/src/WinGet.RestSource.sln index 8a4be2f9..9488e1cc 100644 --- a/src/WinGet.RestSource.sln +++ b/src/WinGet.RestSource.sln @@ -53,7 +53,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowershellModule", "Powersh EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{954044E2-C2B4-4F62-BE4B-D9FAF66B05C2}" ProjectSection(SolutionItems) = preProject - ..\Tools\PowershellModule\doc\new-winget-rest-source-azure.md = ..\Tools\PowershellModule\doc\new-winget-rest-source-azure.md + ..\Tools\PowershellModule\doc\WingetRestSource.md = ..\Tools\PowershellModule\doc\WingetRestSource.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3C510F11-3A5B-4E30-8189-05E87B7DE1F0}" From 498ecf4ec6ef9a5a0a4a07988575bab258ae54fd Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:13:06 -0800 Subject: [PATCH 02/16] stage 1 --- .../src/Library/Add-AzureResourceGroup.ps1 | 33 ++-- .../src/Library/Add-WinGetManifest.ps1 | 2 +- .../src/Library/Get-PairedAzureRegion.ps1 | 70 ++++----- .../src/Library/Get-WinGetManifest.ps1 | 147 +++++++----------- .../src/Library/New-ARMParameterObject.ps1 | 52 +++---- .../src/Library/New-WinGetSource.ps1 | 11 +- .../src/Library/Remove-WinGetManifest.ps1 | 2 +- .../src/Library/Test-AzureResource.ps1 | 91 ++++++----- .../src/Library/Test-ConnectionToAzure.ps1 | 18 ++- .../src/Library/Test-WinGetManifest.ps1 | 42 +++-- .../src/Library/WingetClasses.ps1 | 17 +- .../src/Microsoft.WinGet.Source.psd1 | 10 +- .../src/Microsoft.WinGet.Source.psm1 | 23 +-- 13 files changed, 238 insertions(+), 280 deletions(-) diff --git a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 index 82289f45..b10ceb20 100644 --- a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 +++ b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 @@ -34,22 +34,12 @@ Function Add-AzureResourceGroup BEGIN { $Return = $false - $ErrorMessageRGDoesNotExist = "*Provided resource group does not exist*" - $SupportedRegions = @("eastasia", "southeastasia", "centralus", "eastus", "eastus2", "westus", "northcentralus", - "southcentralus", "northeurope", "southeurope", "westeurope", "japanwest", "japaneast", - "brazilsouth", "australiaeast", "australiasoutheast", "southindia", "centralindia", "westindia", - "canadacentral", "canadaeast", "uksouth", "ukwest", "westcentralus", "westus2", "koreacentral", - "koreasouth", "francecentral", "francesouth", "australiacentral", "australiacentral2", "uaecentral", - "uaenorth", "southafricanorth", "southafricawest") - - if($Name.Contains("-")) { - $Name = $("$Name").Replace("-","") - Write-Information -MessageData "Removed special characters from the Azure Resource Group Name (New Name: $Name)." - } - - if($Region -and !$SupportedRegions.Contains($Region.ToLower())) { - ## Provided Azure Region does not match supported regions in $SupportedRegions variable. - Write-Warning -Message "Provided Azure region $Region is not in the list of supported Azure Regions. Will attempt to create Resource Group in the provided Region." + + ## Normalize resource group name + $NormalizedName = $Name -replace "[^a-zA-Z0-9-()_.]", "" + if($Name -cne $NormalizedName) { + $Name = $NormalizedName + Write-Warning "Removed special characters from the Azure Resource Group Name (New Name: $Name)." } } PROCESS @@ -62,19 +52,20 @@ Function Add-AzureResourceGroup $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable err -InformationAction SilentlyContinue -WarningAction SilentlyContinue if(!$Result) { - if($err.Where({$_ -like $ErrorMessageRGDoesNotExist})) { - Write-Verbose -Message "Resource Group does not exist, will create $Name in the specified $Region." - } + Write-Information "Failed to retrieve Resource Group, will attempt to create $Name in the specified $Region." $Result = New-AzResourceGroup -Name $Name -Location $Region if($Result) { - Write-Information -MessageData "Resource Group $Name has been created in the $Region region." + Write-Information "Resource Group $Name has been created in the $Region region." $Return = $true } + else { + Write-Error "Failed to retrieve or create Resource Group with name $Name." + } } else { ## Found an existing Resource Group matching the name of $Name - Write-Warning -Message "Found an existing Resource Group matching the name of $Name. Will not create a new Resource Group." + Write-Warning "Found an existing Resource Group matching the name of $Name. Will not create a new Resource Group." $Return = $true } } diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index 8f8f6048..31096335 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -83,7 +83,7 @@ Function Add-WinGetManifest ############################### ## Verify Azure Resources Exist Write-Verbose -Message "Verifying that the Azure Resource $AzureFunctionName exists.." - $Result = Test-AzureResource -FunctionName $AzureFunctionName -ResourceGroup $AzureResourceGroupName + $Result = Test-AzureResource -ResourceName $AzureFunctionName -ResourceGroup $AzureResourceGroupName if(!$Result) { throw "Failed to confirm resources exist in Azure. Please verify and try again." } diff --git a/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 b/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 index 96edd09f..9ec7b4f7 100644 --- a/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 +++ b/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 @@ -12,41 +12,7 @@ Function Get-PairedAzureRegion } PROCESS { - if($Region -like "*us*" -and $Region -notlike "*australia*") { - switch ($Region) { - "eastus" { - $Result = "westus" - } - "westus" { - $Result = "eastus" - } - "eastus2" { - $Result = "centralus" - } - "centralus" { - $Result = "eastus2" - } - "northcentralus" { - $Result = "southcentralus" - } - "southcentralus" { - $Result = "northcentralus" - } - "westus2" { - $Result = "westcentralus" - } - "westcentralus" { - $Result = "westus2" - } - "westus3" { - $Result = "westcentralus" - } - Default { - $Result = "westus" - } - } - } - elseif($Region -like "*canada*") { + if($Region -like "*canada*") { switch ($Region) { "canadacentral" { $Result = "canadaeast" @@ -130,6 +96,40 @@ Function Get-PairedAzureRegion } } } + elseif($Region -like "*us*") { + switch ($Region) { + "eastus" { + $Result = "westus" + } + "westus" { + $Result = "centralus" + } + "eastus2" { + $Result = "centralus" + } + "centralus" { + $Result = "eastus2" + } + "northcentralus" { + $Result = "southcentralus" + } + "southcentralus" { + $Result = "northcentralus" + } + "westus2" { + $Result = "westcentralus" + } + "westcentralus" { + $Result = "westus2" + } + "westus3" { + $Result = "westcentralus" + } + Default { + $Result = "westus" + } + } + } elseif($Region -like "*india*") { switch ($Region) { "westindia" { diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index b8c2a716..1b5b5547 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -5,7 +5,7 @@ Function Get-WinGetManifest { <# .SYNOPSIS - Connects to the specified source REST API, or local file system path to retrieve the package Manifests, returning + Connects to the specified source REST API, or local file system path to retrieve the package manifests, returning the manifest found. Allows for retrieving results based on the package identifier when targetting the REST APIs. .DESCRIPTION @@ -25,7 +25,7 @@ Function Get-WinGetManifest the same package manifest. .PARAMETER JSON - A JSON string containing a single application's REST source Packages Manifest that will be merged with locally processed files. This is + A JSON string containing a single application's REST source Packages Manifest that will be merged with locally processed .yaml files. This is used by the script infrastructure internally and is not expected to be useful to an end user using this command. .PARAMETER URL @@ -82,20 +82,9 @@ Function Get-WinGetManifest ## Determines the PowerShell Parameter Set that was used in the call of this Function. switch ($PsCmdlet.ParameterSetName) { "Azure" { - ############################### - ## Validates that the Azure Modules are installed - Write-Verbose -Message "Testing required PowerShell modules are installed." - - $RequiredModules = @("Az.Resources", "Az.Accounts", "Az.Websites", "Az.Functions") - $Result = Test-PowerShellModuleExist -Modules $RequiredModules - - if(!$Result) { - throw "Unable to run script, missing required PowerShell modules" - } - ############################### ## Connects to Azure, if not already connected. - Write-Verbose -Message "Testing connection to Azure." + Write-Verbose "Testing connection to Azure." $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { @@ -109,14 +98,14 @@ Function Get-WinGetManifest $AzureResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName if($AzureResourceGroupName) { - $Result = Test-AzureResource -FunctionName $FunctionName -ResourceGroup $AzureResourceGroupName + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $AzureResourceGroupName + + if(!$Result) { + throw "Failed to confirm resources exist in Azure. Please verify and try again." + } } else { - throw "Unable to locate Function (""$FunctionName"") in the Azure Tenant." - } - - if(!$Result) { - throw "Failed to confirm resources exist in Azure. Please verify and try again." + throw "Unable to locate Function (""$FunctionName"") with Resource Group in the Azure Tenant." } if($PackageIdentifier){ @@ -128,87 +117,76 @@ Function Get-WinGetManifest ## Specifies the REST api call that will be performed $TriggerName = "ManifestGet" - $apiContentType = "application/json" - $apiMethod = "Get" + $ApiContentType = "application/json" + $ApiMethod = "Get" ## Creates the API Post Header - $apiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $apiHeader.Add("Accept", 'application/json') + $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $ApiHeader.Add("Accept", 'application/json') } "File" { + ## Convert to full path if applicable + $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) + $ManifestFileExists = Test-Path -Path $Path if(!$ManifestFileExists) { - ## The target path does not exist $ErrReturnObject = @{ FilePath = $Path ManifestFileExists = $ManifestFileExists } Write-Error -Message "Target path did not point to an object." -TargetObject $ErrReturnObject - return + return $Return } - $PathProperties = Get-ItemProperty $Path - $ManifestFile = "" + $PathItem = Get-Item $Path + $ManifestFile = "" + $ApplicationManifest = "" + $ManifestFileType = "" - if($PathProperties.Attributes -like "*Directory*") { + if($PathItem.PSIsContainer) { ## $Path variable is pointing at a directory $PathChildItemsJSON = Get-ChildItem -Path $Path -Filter "*.json" $PathChildItemsYAML = Get-ChildItem -Path $Path -Filter "*.yaml" - $VerboseMessage = "Path pointed to a directory, found $($PathChildItemsJSON.count) JSON files, and $($PathChildItemsYAML.count) YAML files." - Write-Verbose -Message $VerboseMessage + Write-Verbose -Message "Path pointed to a directory, found $($PathChildItemsJSON.count) JSON files, and $($PathChildItemsYAML.count) YAML files." + + $ErrReturnObject = @{ + JSONFiles = $PathChildItemsJSON + YAMLFiles = $PathChildItemsYAML + JSONCount = $PathChildItemsJSON.count + YAMLCount = $PathChildItemsYAML.count + } ## Validating found objects if($PathChildItemsJSON.count -eq 0 -and $PathChildItemsYAML.count -eq 0) { ## No JSON or YAML files were found in the directory. - $ErrorMessage = "Directory does not contain any combination of JSON and YAML files." - $ErrReturnObject = @{ - JSONFiles = $PathChildItemsJSON - YAMLFiles = $PathChildItemsYAML - JSONCount = $PathChildItemsJSON.count - YAMLCount = $PathChildItemsYAML.count - } - - $ManifestFileType = "Error" + $ErrorMessage = "Directory does not contain any JSON or YAML files." Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + return $Return } elseif($PathChildItemsJSON.count -gt 0 -and $PathChildItemsYAML.count -gt 0) { ## A combination of JSON and YAML Files were found. $ErrorMessage = "Directory contains a combination of JSON and YAML files." - $ErrReturnObject = @{ - JSONFiles = $PathChildItemsJSON - YAMLFiles = $PathChildItemsYAML - JSONCount = $PathChildItemsJSON.count - YAMLCount = $PathChildItemsYAML.count - } - - $ManifestFileType = "Error" Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + return $Return } elseif($PathChildItemsJSON.count -gt 1) { ## More than one Package Manifest's JSON files was found. $ErrorMessage = "Directory contains more than one JSON file." - $ErrReturnObject = @{ - JSONFiles = $PathChildItemsJSON - YAMLFiles = $PathChildItemsYAML - JSONCount = $PathChildItemsJSON.count - YAMLCount = $PathChildItemsYAML.count - } - - $ManifestFileType = "Error" Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + return $Return } elseif($PathChildItemsJSON.count -eq 1) { ## Single JSON has been found in the target folder. Write-Verbose -Message "Single JSON has been found in the specified directory." $ManifestFile = $PathChildItemsJSON $ApplicationManifest = Get-Content -Path $PathChildItemsJSON.FullName -Raw - $ManifestFileType = $PathChildItemsJSON.Extension + $ManifestFileType = ".json" } elseif($PathChildItemsYAML.count -gt 0) { - Write-Verbose -Message "Single YAML has been found in the specified directory." + Write-Verbose -Message "YAML has been found in the specified directory." ## YAML has been found in the target folder. $ManifestFile = $PathChildItemsYAML $ManifestFileType = ".yaml" @@ -244,13 +222,13 @@ Function Get-WinGetManifest $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default - $apiHeader.Add("x-functions-key", $FunctionKey) + $ApiHeader.Add("x-functions-key", $FunctionKey) $AzFunctionURL = "https://" + $DefaultHostName + "/api/" + "packageManifests" + $PackageIdentifier ## Publishes the Manifest to the Windows Package Manager REST source Write-Verbose -Message "Invoking the REST API call." - $Results = Invoke-RestMethod $AzFunctionURL -Headers $apiHeader -Method $apiMethod -ContentType $apiContentType + $Results = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType Write-Verbose "Found ($($Results.Data.Count)) Manifests that matched." foreach ($Result in $Results.Data){ @@ -268,48 +246,33 @@ Function Get-WinGetManifest ## Sets the return result to be the contents of the JSON file if the Manifest test passed. $Return = $ApplicationManifest - Write-Verbose -Message "Returned Manifest from JSON file: $($Return.PackageIdentifier)" + Write-Information "Returned Manifest from JSON file: $($Return.PackageIdentifier)" } } ## If the path resolves to a YAML file ".yaml" { ## Directory - *.yaml files included within. - if($WinGetDesktopAppInstallerLibLoaded) { - Write-Verbose -Message "YAML Files have been found in the target directory. Building a JSON manifest with found files." - if($Json){ - Write-Verbose "JSON" - $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $JSON.GetJson()); - } - else{ - Write-Verbose "Other" - try { - $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, ""); - } - catch { - Write-Verbose "Attempt to convert YAML to JSON failed." - } - - } - - Write-Verbose -Message "Returned Manifest from YAML file: $($Return.PackageIdentifier)" + Write-Verbose -Message "YAML Files have been found in the target directory. Building a JSON manifest with found files." + if($Json){ + Write-Verbose "Prior manifest provided. New manifest will be merged with prior manifest." + $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $JSON.GetJson()); } - else { - Write-Error -Message "Unable to process YAML files. Re-import the module to reload the required dependencies." -Category ResourceUnavailable + else{ + Write-Verbose "Prior manifest not provided." + $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, ""); } - } - "Error" { + + Write-Information "Returned Manifest from YAML file: $($Return.PackageIdentifier)" } default { - if($ManifestFileExists) { - $ErrorMessage = "Incorrect filetype. Verify the file is of type '*.yaml' or '*.json' and try again." - $ErrReturnObject = @{ - ApplicationManifest = $ApplicationManifest - ManifestFile = $ManifestFile - $ManifestFileType = $ManifestFileType - } - - Write-Error -Message $ErrorMessage -Category InvalidType -TargetObject $ErrReturnObject + $ErrorMessage = "Incorrect file type. Verify the file is of type '*.yaml' or '*.json' and try again." + $ErrReturnObject = @{ + ApplicationManifest = $ApplicationManifest + ManifestFile = $ManifestFile + $ManifestFileType = $ManifestFileType } + + Write-Error -Message $ErrorMessage -Category InvalidType -TargetObject $ErrReturnObject } } } diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 index 3d8244e9..81793f42 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 @@ -24,17 +24,17 @@ Function New-ARMParameterObject The Azure location where objects will be created in. .PARAMETER ImplementationPerformance - ["Demo", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. + ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. .EXAMPLE - New-ARMParameterObject -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Demo" + New-ARMParameterObject -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Developer" Creates the Parameter files required for the creation of the ARM objects. #> PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$ParameterFolderPath, - [Parameter(Position=1, Mandatory=$false)][string]$TemplateFolderPath = "$PSScriptRoot\ARMTemplate", + [Parameter(Position=1, Mandatory=$false)][string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplate", [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance @@ -42,29 +42,30 @@ Function New-ARMParameterObject BEGIN { ## The Names that are to be assigned to each resource. - $AppInsightsName = $Name - $KeyVaultName = $Name - $StorageAccountName = $Name.Replace("-", "") - $aspName = $Name - $CDBAccountName = $Name - $FunctionName = $Name - $FrontDoorName = $Name - $appConfigName = $Name - $aspGenevaName = $Name + $AppInsightsName = "appin-" + $Name -replace "[^a-zA-Z0-9-]", "" + $KeyVaultName = "kv-" + $Name -replace "[^a-zA-Z0-9-]", "" + $StorageAccountName = "st" + $Name.ToLower() -replace "[^a-z0-9]", "" + $aspName = "asp-" + $Name -replace "[^a-zA-Z0-9-]", "" + $CDBAccountName = "cosmos-" + $Name.ToLower() -replace "[^a-z0-9-]", "" + $FunctionName = "azfun-" + $Name -replace "[^a-zA-Z0-9-]", "" + $appConfigName = "appconfig-" + $Name -replace "[^a-zA-Z0-9-]", "" + $apiManagementName = "apim-" + $Name -replace "[^a-zA-Z0-9-]", "" + + ## Not supported in deployment script + ## $FrontDoorName = "" + ## $aspGenevaName = "" ## The names of the Azure Cosmos Database and Container - Do not change (Must match with the values in the compiled ## Windows Package Manager Functions [WinGet.RestSource.Functions.zip]) $CDBDatabaseName = "WinGet" $CDBContainerName = "Manifests" - ## The values required for Function ARM Template + ## The values required for Function ARM Template. But not supported in deployment script. $manifestCacheEndpoint = "" $monitoringTenant = "" $monitoringRole = "" $monitoringMetricsAccount = "" - - ## The values required for the Azure App Config ARM Template - #$appConfigFeatureFlag = "" + $runFromPackageUrl = "" ## Relative Path from the Working Directory to the Azure ARM Template Files $TemplateAppInsightsPath = "$TemplateFolderPath\applicationinsights.json" @@ -92,7 +93,7 @@ Function New-ARMParameterObject Write-Verbose -Message "ARM Parameter Resource performance is based on the: $ImplementationPerformance." switch ($ImplementationPerformance) { - "Demo" { + "Developer" { $KeyVaultSKU = "Standard" $StorageAccountPerformance = "Standard_LRS" $ASPSKU = "B1" @@ -291,7 +292,6 @@ Function New-ARMParameterObject Parameters = @{ appConfigName = @{ value = $appConfigName } # Name used to contain the Storage Account connection string in the Key Value location = @{ value = $Region } # Azure hosting location - #featureFlags = @{ value = $appConfigFeatureFlag } # Feature Flag } } }, @@ -368,20 +368,16 @@ Function New-ARMParameterObject ipRules =@{ value = @( @{ - ipAddressOrRange = "104.42.195.92" - } - - @{ - ipAddressOrRange = "40.76.54.131" + ipAddressOrRange = "13.91.105.215" } @{ - ipAddressOrRange = "52.176.6.30" + ipAddressOrRange = "4.210.172.107" } @{ - ipAddressOrRange = "52.169.50.45" + ipAddressOrRange = "13.88.56.148" } @{ - ipAddressOrRange = "52.187.184.26" + ipAddressOrRange = "40.91.218.243" } @{ ipAddressOrRange = "0.0.0.0" @@ -392,8 +388,8 @@ Function New-ARMParameterObject value = @{ type = "Periodic" periodicModeProperties = @{ - backupIntervalInMinutes = 240 - backupRetentionIntervalInHours = 720 + backupIntervalInMinutes = 240 + backupRetentionIntervalInHours = 720 } } } diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index 4329c0c2..5c98ba66 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -35,10 +35,10 @@ Function New-WinGetSource [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\Library\RestAPI\WinGet.RestSource.Functions.zip) .PARAMETER ImplementationPerformance - [Optional] ["Demo", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. + [Optional] ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. | Preference | Description | |------------|-------------------------------------------------------------------------------------------------------------------------| - | Demo | Specifies lowest cost for demonstrating the Windows Package Manager REST source. Uses free-tier options when available. | + | Developer | Specifies lowest cost for developing the Windows Package Manager REST source. Uses free-tier options when available. | | Basic | Specifies a basic functioning Windows Package Manager REST source. | | Enhanced | Specifies a higher tier functionality with data replication across multiple data centers. | @@ -68,15 +68,14 @@ Function New-WinGetSource [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", [Parameter(Position=4, Mandatory=$false)] [string]$ParameterOutput = $(Get-Location).Path, [Parameter(Position=5, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\RestAPI\WinGet.RestSource.Functions.zip", - [ValidateSet("Demo", "Basic", "Enhanced")] + [ValidateSet("Developer", "Basic", "Enhanced")] [Parameter(Position=6, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", [Parameter()] [switch]$ShowConnectionInstructions ) BEGIN { - if($ImplementationPerformance -eq "Demo") { - $WarningMessage = "`n The ""Demo"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this.`n`n" - Write-Warning -Message $WarningMessage + if($ImplementationPerformance -eq "Developer") { + Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this.`n" } ## Paths to the Parameter and Template folders and the location of the Function Zip diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index 8b3e3428..a3baf7d6 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -67,7 +67,7 @@ Function Remove-WinGetManifest ############################### ## Verify Azure Resources Exist - $Result = Test-AzureResource -FunctionName $FunctionName -ResourceGroup $AzureResourceGroupName + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $AzureResourceGroupName if(!$Result) { throw "Failed to confirm resources exist in Azure. Please verify and try again." } diff --git a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 b/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 index f58dea91..861b283a 100644 --- a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 +++ b/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 @@ -4,81 +4,90 @@ Function Test-AzureResource { <# .SYNOPSIS - Returns a boolean result validating that the Resource Group and Function exist. + Returns a boolean result validating that the Resource Group and Resource exist. .DESCRIPTION - Returns a boolean result validating that the Resource Group and Function exist. + Returns a boolean result validating that the Resource Group and Resource exist. .PARAMETER ResourceGroup The Resource Group that the objects will be tested in reference to. - .PARAMETER FunctionName - Name of the Azure function name. + .PARAMETER ResourceName + Name of the Azure Resource name. .EXAMPLE - Test-AzureResource -ResourceGroup "WinGet" -FunctionName "contosorestsource" + Test-AzureResource -ResourceGroup "WinGet" -ResourceName "contosorestsource" - Returns a boolean result validating that the Resource Group and Function exist. + Returns a boolean result validating that the Resource Group and Resource exist. #> PARAM( - [Parameter(Position=0, Mandatory = $false)] [string]$ResourceGroup, - [Parameter(Position=1, Mandatory = $false)] [string]$FunctionName + [Parameter(Position=0, Mandatory = $true)] [string]$ResourceGroup, + [Parameter(Position=1, Mandatory = $true)] [string]$ResourceName, + [ValidateSet("Function")] + [Parameter(Position=2, Mandatory = $false)] [string]$ResourceType = "Function" ) BEGIN { - $Result = $true + $Result = $false $AzureResourceGroupName = $ResourceGroup - $AzureFunctionName = $FunctionName - - $AzureFunctionNameNotNullOrEmpty = $true - $AzureFunctionNameExists = $true - - $AzureResourceGroupNameNotNullOrEmpty = $true - $AzureResourceGroupNameExists = $true + $AzureResourceName = $ResourceName - ## Determines if the Azure Function App Name is not null or empty - if($AzureFunctionName.Length -le 0) { - $AzureFunctionName = "" - $AzureFunctionNameNotNullOrEmpty = $false - } - - ## Determines if the Azure Function App Name is in Azure - if($(Get-AzFunctionApp).Where({$_.Name -eq $AzureFunctionName}).Count -le 0) { - $AzureFunctionNameExists = $false - } + $AzureResourceGroupNameNullOrWhiteSpace = $false + $AzureResourceGroupNameExists = $false + + $AzureResourceNameNullOrWhiteSpace = $false + $AzureResourceNameExists = $false ##Determines if the Azure Resource Group Name is not null or empty - if($AzureResourceGroupName.Length -le 0) { - $AzureResourceGroupName = "" - $AzureResourceGroupNameNotNullOrEmpty = $false + if([string]::IsNullOrWhiteSpace($AzureResourceGroupName)) { + $AzureResourceGroupName = "" + $AzureResourceGroupNameNullOrWhiteSpace = $true } - if($(Get-AzResourceGroup).Where({$_.ResourceGroupName -eq $AzureResourceGroupName}).Count -lt 0) { - $AzureResourceGroupNameExists = $false + if($(Get-AzResourceGroup).Where({$_.ResourceGroupName -eq $AzureResourceGroupName}).Count -gt 0) { + $AzureResourceGroupNameExists = $true } - } + + ## Determines if the Azure Resource Name is not null or empty + if([string]::IsNullOrWhiteSpace($AzureResourceName)) { + $AzureResourceName = "" + $AzureResourceNameNullOrWhiteSpace = $true + } + + ## Determines if the Azure Resource Name is in Azure + if ($AzureResourceGroupNameExists) { + switch ($ResourceType) { + "Function" { + if($(Get-AzFunctionApp -ResourceGroupName $AzureResourceGroupName).Where({$_.Name -eq $AzureResourceName}).Count -gt 0) { + $AzureResourceNameExists = $true + } + } + } + } + } PROCESS { $VerboseMessage = "Azure Resources:`n" - $VerboseMessage += " Azure Function Exists: $AzureFunctionNameExists`n" - $VerboseMessage += " Azure Resource Group Exists: $AzureResourceGroupNameExists" + $VerboseMessage += " Azure Resource Group Exists: $AzureResourceGroupNameExists`n" + $VerboseMessage += " Azure Resource Exists: $AzureResourceNameExists" Write-Verbose -Message $VerboseMessage - ## If either the Azure Function Name or the Azure Resource Group Name are null, error. - if(!$AzureFunctionNameNotNullOrEmpty -or !$AzureResourceGroupNameNotNullOrEmpty -or !$AzureFunctionNameExists -or !$AzureResourceGroupNameExists) { - $ErrorMessage = "Both the Azure Function and Resource Group Names can not be null and must exist. Please verify that the Azure function and Resource Group exist." + ## If either the Azure Resource Name or the Azure Resource Group Name are null, error. + if($AzureResourceGroupNameNullOrWhiteSpace -or $AzureResourceNameNullOrWhiteSpace -or !$AzureResourceGroupNameExists -or !$AzureResourceNameExists) { + $ErrorMessage = "Both the Azure Resource Group and Resource Names can not be null and must exist. Please verify that the Azure Resource Group and Resource exist." $ErrReturnObject = @{ - AzureFunctionNameNotNullOrEmpty = $AzureFunctionNameNotNullOrEmpty - AzureResourceGroupNameNotNullOrEmpty = $AzureResourceGroupNameNotNullOrEmpty - AzureFunctionNameExists = $AzureFunctionNameExists + AzureResourceGroupNameNullOrWhiteSpace = $AzureResourceGroupNameNullOrWhiteSpace + AzureResourceNameNullOrWhiteSpace = $AzureResourceNameNullOrWhiteSpace AzureResourceGroupNameExists = $AzureResourceGroupNameExists + AzureResourceNameExists = $AzureResourceNameExists Result = $false } Write-Error -Message $ErrorMessage -Category InvalidArgument -TargetObject $ErrReturnObject - $Result = $false } + + $Result = $AzureResourceGroupNameExists -and $AzureResourceNameExists } END { diff --git a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 index 8c078632..26f3c622 100644 --- a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 @@ -46,23 +46,25 @@ Function Test-ConnectionToAzure PROCESS { if($AzContext) { - Write-Verbose -Message "Connected to Azure" - $Result = $true - - if($AzContext.Subscription.Name -ne $SubscriptionName -and $SubscriptionName) { + if($SubscriptionName -and $AzContext.Subscription.Name -ne $SubscriptionName) { ## If Subscription Name paramter is passed in, and the value doesn't match current connection return $false - Write-Verbose -Message "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionName" + Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionName" $Result = $false } - if($AzContext.Subscription.Id -ne $SubscriptionId -and $SubscriptionId) { + elseif($SubscriptionId -and $AzContext.Subscription.Id -ne $SubscriptionId) { ## If Subscription Id paramter is passed in, and the value doesn't match current connection return $false - Write-Verbose -Message "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionId" + Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionId" $Result = $false } + else { + Write-Information "Connected to Azure" + $Result = $true + } } else { ## Not currently connected to Azure - Write-Verbose -Message "Not connected to Azure, please connect to your Azure Subscription" + Write-Error "Not connected to Azure, please connect to your Azure Subscription" + $Result = $false } } END diff --git a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 index 91fdaa06..3211e828 100644 --- a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 @@ -27,25 +27,36 @@ Function Test-WinGetManifest [CmdletBinding(DefaultParameterSetName = 'File')] PARAM( [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, - [Parameter(Position=0, Mandatory=$true, ParameterSetName="Object")]$Manifest, - [Parameter(Position=1, Mandatory=$false)] [switch]$ReturnManifest + [Parameter(Position=0, Mandatory=$true, ParameterSetName="Object")] $Manifest ) BEGIN { Write-Verbose -Message "Validating the Manifest ($($PSCmdlet.ParameterSetName))." + + $Return = $true + switch ($($PSCmdlet.ParameterSetName)) { "File"{ - if((Test-Path -Path $Path)) { - ## Retrieves the contents of the Manifest file - $Manifest = Get-Content -Path $Path -Raw + ## Convert to full path if applicable + $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) + + $PathFound = Test-Path -Path $Path; + + if ($PathFound) + { + ## Construct $Manifest from path then validate + } + else + { + Write-Error "Manifest path not found: $Path" + $Return = $false; } } "Object" { } } - $ManifestFileTypeJSON = $false - $Return = $true + } PROCESS { @@ -53,20 +64,7 @@ Function Test-WinGetManifest } END { - Write-Verbose -Message "Testing the Manifest has passed: $Return" - ## Determines what will be returned from the Function - if($Return) { - ## Returns the Manifest only if the test passes. If the test fails return False - if($Return) { - return $Manifest - } - else { - return $Return - } - } - else { - ## Returns the result of the test. If all test pass, the result is True, otherwise will return False - return $Return - } + Write-Information "Testing the Manifest has passed: $Return" + return $Return } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/WingetClasses.ps1 b/Tools/PowershellModule/src/Library/WingetClasses.ps1 index 53de81be..14e829e6 100644 --- a/Tools/PowershellModule/src/Library/WingetClasses.ps1 +++ b/Tools/PowershellModule/src/Library/WingetClasses.ps1 @@ -234,15 +234,16 @@ class PackageManifest $this.Versions.Add([WingetVersion]::new($a)) $i = $this.Versions.Count -1 - foreach ($installer in $b){ + foreach ($installer in $b) { $this.Versions[$i].AddInstaller($installer) } - foreach ($locale in $c){ - $this.Versions.AddLocale($locale) - - if($locale.ManifestType -eq "defaultLocale"){ + foreach ($locale in $c) { + if ($locale.ManifestType -eq "defaultLocale") { $this.Versions.AddDefaultLocale($locale) } + else { + $this.Versions.AddLocale($locale) + } } } ConvertFromYAML ($a) @@ -254,15 +255,15 @@ class PackageManifest foreach ($file in $a){ $FileContents = Get-Content -Raw -Path $file.FullName - if ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.installer')){ + if ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.installer')) { $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents $YAMLInstallers.Add(($ConvertedContents)) } - elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.defaultLocale')){ + elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.defaultLocale')) { $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents $YAMLLocales.Add(($ConvertedContents)) } - elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.version')){ + elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.version')) { $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents $YAMLVersions.Add(($ConvertedContents)) } diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index 2b0f194f..8cf98ca1 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -10,6 +10,9 @@ # Version number of this module. ModuleVersion = '0.1.0' + + # Compatible PowerShell edition + CompatiblePSEditions = @('Core') # ID used to uniquely identify this module GUID = 'b70c845d-ddb1-4454-bfc2-a874783c2d04' @@ -24,7 +27,11 @@ Description = 'This module provides support for working with Windows Package Manager REST based sources.' # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '5.1' + PowerShellVersion = '7.4' + + # Required modules. + # Due to issue https://github.com/PowerShell/PowerShell/issues/11190, using RequiredModules will greatly slow down the import module. We'll handle them manually in psm1. + # RequiredModules = @('Az', 'powershell-yaml') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Convert-YamlToJson") @@ -47,6 +54,7 @@ # A URL to the main website for this project. ProjectUri = 'https://github.com/microsoft/winget-cli-restsource' + # Release channel PreRelease = 'alpha' } } diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index 9a9948de..30448e71 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -7,21 +7,13 @@ ## Loads Libraries Get-ChildItem -Path "$PSScriptRoot\Library" -Filter *.ps1 | foreach-object { . $_.FullName } -[string] $WinGetPSEdition = "Core" -[string] $WinGetPSVersion = "7.4.0" - ## Loads the binaries from the Desktop App Installer Library - Only if running PowerShell at a specified edition -if ($PSEdition -eq $WinGetPSEdition && [System.Version]$PSVersion -ge [System.Version]$WinGetPSVersion) { - try { - Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" - } - catch { - ## Exceptions thrown by Add-Type will not fail the Import-Module. Catch and re-throw to fail the Import-Module. - throw $_ - } +try { + Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" } -else { - throw "Unable to load required binaries. Verify you are using Powershell $WinGetPSEdition or later." +catch { + ## Exceptions thrown by Add-Type will not fail the Import-Module. Catch and re-throw to fail the Import-Module. + throw $_ } ## Validates that the required Azure Modules are present when the script is imported. @@ -41,6 +33,5 @@ foreach ($RequiredModule in $RequiredModules) { [Boolean] $TestResult = Test-PowerShellModuleExist -Modules $RequiredModules if (!$TestResult) { ## Modules have been identified as missing - $ErrorMessage = "There are missing PowerShell modules that must be installed. Some or all PowerShell functions included in this library will fail." - throw $ErrorMessage -} \ No newline at end of file + throw "There are missing PowerShell modules that must be installed. Some or all PowerShell functions included in this library will fail." +} From 877fed1dc9756ba8012379c69a15d27727441ae3 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:51:12 -0800 Subject: [PATCH 03/16] stage 2 --- .../PowershellModule/src/Library/Classes.ps1 | 436 ------------------ .../src/Library/Convert-YAMLToJSON.ps1 | 36 -- .../src/Library/Get-WinGetManifest.ps1 | 6 +- .../src/Library/WinGetManifest.ps1 | 121 +++++ .../src/Library/WingetClasses.ps1 | 275 ----------- .../src/Microsoft.WinGet.Source.psd1 | 2 +- .../src/Microsoft.WinGet.Source.psm1 | 9 +- .../Models/ExtendedSchemas/VersionExtended.cs | 4 +- src/WinGet.RestSource.sln | 5 +- 9 files changed, 137 insertions(+), 757 deletions(-) delete mode 100644 Tools/PowershellModule/src/Library/Classes.ps1 delete mode 100644 Tools/PowershellModule/src/Library/Convert-YAMLToJSON.ps1 create mode 100644 Tools/PowershellModule/src/Library/WinGetManifest.ps1 delete mode 100644 Tools/PowershellModule/src/Library/WingetClasses.ps1 diff --git a/Tools/PowershellModule/src/Library/Classes.ps1 b/Tools/PowershellModule/src/Library/Classes.ps1 deleted file mode 100644 index dd1ab4da..00000000 --- a/Tools/PowershellModule/src/Library/Classes.ps1 +++ /dev/null @@ -1,436 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -class WinGetLocale -{ - $Moniker - $PackageLocale - $Publisher - $PublisherUrl - $PublisherSupportUrl - $PrivacyUrl - $Author - $PackageName - $PackageUrl - $License - $LicenseUrl - $Copyright - $CopyRightUrl - $ShortDescription - $Description - $ReleaseNotes - $ReleaseNotesUrl - $Agreements - $Tags - - WinGetLocale ([string] $a) - { - $Converted = $a | ConvertFrom-Json - - $this.Moniker = $Converted.Moniker - $this.PackageLocale = $Converted.PackageLocale - $this.Publisher = $Converted.Publisher - $this.PublisherUrl = $Converted.PublisherUrl - $this.PublisherSupportUrl = $Converted.PublisherSupportUrl - $this.PrivacyUrl = $Converted.PrivacyUrl - $this.Author = $Converted.Author - $this.PackageName = $Converted.PackageName - $this.PackageUrl = $Converted.PackageUrl - $this.License = $Converted.License - $this.LicenseUrl = $Converted.LicenseUrl - $this.Copyright = $Converted.Copyright - $this.CopyRightUrl = $Converted.CopyRightUrl - $this.ShortDescription = $Converted.ShortDescription - $this.Description = $Converted.Description - $this.ReleaseNotes = $Converted.ReleaseNotes - $this.ReleaseNotesUrl = $Converted.ReleaseNotesUrl - $this.Agreements = $Converted.Agreements - $this.Tags = $Converted.Tags - } - WinGetLocale ([WinGetLocale] $a) - { - $this.Moniker = $a.Moniker - $this.PackageLocale = $a.PackageLocale - $this.Publisher = $a.Publisher - $this.PublisherUrl = $a.PublisherUrl - $this.PublisherSupportUrl = $a.PublisherSupportUrl - $this.PrivacyUrl = $a.PrivacyUrl - $this.Author = $a.Author - $this.PackageName = $a.PackageName - $this.PackageUrl = $a.PackageUrl - $this.License = $a.License - $this.LicenseUrl = $a.LicenseUrl - $this.Copyright = $a.Copyright - $this.CopyRightUrl = $a.CopyRightUrl - $this.ShortDescription = $a.ShortDescription - $this.Description = $a.Description - $this.ReleaseNotes = $a.ReleaseNotes - $this.ReleaseNotesUrl = $a.ReleaseNotesUrl - $this.Agreements = $a.Agreements - $this.Tags = $a.Tags - } - WinGetLocale ([psobject] $a) { - $this.Moniker = $a.Moniker - $this.PackageLocale = $a.PackageLocale - $this.Publisher = $a.Publisher - $this.PublisherUrl = $a.PublisherUrl - $this.PublisherSupportUrl = $a.PublisherSupportUrl - $this.PrivacyUrl = $a.PrivacyUrl - $this.Author = $a.Author - $this.PackageName = $a.PackageName - $this.PackageUrl = $a.PackageUrl - $this.License = $a.License - $this.LicenseUrl = $a.LicenseUrl - $this.Copyright = $a.Copyright - $this.CopyRightUrl = $a.CopyRightUrl - $this.ShortDescription = $a.ShortDescription - $this.Description = $a.Description - $this.ReleaseNotes = $a.ReleaseNotes - $this.ReleaseNotesUrl = $a.ReleaseNotesUrl - $this.Agreements = $a.Agreements - $this.Tags = $a.Tags - } - - [WinGetLocale[]] Add ([WinGetLocale] $a) - { - $FirstValue = [WinGetLocale]::New($this) - $SecondValue = [WinGetLocale]::New($a) - - [WinGetLocale[]]$Result = @([WinGetLocale]::New($FirstValue), [WinGetLocale]::New($SecondValue)) - - Return $Result - } - - [WinGetLocale[]] Add ([psobject]$a) - { - $FirstValue = [WinGetLocale]::New($this) - $SecondValue = [WinGetLocale]::New($a) - - [WinGetLocale[]] $Combined = @([WinGetLocale]::New($FirstValue), [WinGetLocale]::New($SecondValue)) - - Return $Combined - } - - [WinGetLocale[]] Add ([String[]]$a) - { - $FirstValue = [WinGetLocale]::New($this) - $SecondValue = [WinGetLocale]::New($a) - - [WinGetLocale[]] $Combined = @([WinGetLocale]::New($FirstValue), [WinGetLocale]::New($SecondValue)) - - Return $Combined - } -} - -class WinGetInstaller -{ - $InstallerIdentifier - $InstallerSha256 - $InstallerUrl - $Architecture - $InstallerLocale - $Platform - $MinimumOsVersion - $InstallerType - $Scope - $SignatureSha256 - $InstallModes - $InstallerSwitches - $InstallerSuccessCodes - $ExpectedReturnCodes - $UpgradeBehavior - $Commands - $Protocols - $FileExtensions - $Dependencies - $PackageFamilyName - $ProductCode - $Capabilities - $RestricedCapabilities - $MSStoreProductIdentifier - $InstallerAbortsTerminal - $ReleaseDate - $InstallLocationRequired - $RequireExplicitUpgrade - $ElevationRequirement - $UnsupportedOSArchitectures - $AppsAndFeaturesEntries - $Markets - $DownloadCommandProhibited - $RepairBehavior - $ArchiveBinariesDependOnPath - - WinGetInstaller ([string] $a) - { - $Converted = $a | ConvertFrom-Json - - $this.InstallerIdentifier = $Converted.InstallerIdentifier - $this.InstallerSha256 = $Converted.InstallerSha256 - $this.InstallerUrl = $Converted.InstallerUrl - $this.Architecture = $Converted.Architecture - $this.InstallerLocale = $Converted.InstallerLocale - $this.Platform = $Converted.Platform - $this.MinimumOsVersion = $Converted.MinimumOsVersion - $this.InstallerType = $Converted.InstallerType - $this.Scope = $Converted.Scope - $this.SignatureSha256 = $Converted.SignatureSha256 - $this.InstallModes = $Converted.InstallModes - $this.InstallerSwitches = $Converted.InstallerSwitches - $this.InstallerSuccessCodes = $Converted.InstallerSuccessCodes - $this.ExpectedReturnCodes = $Converted.ExpectedReturnCodes - $this.UpgradeBehavior = $Converted.UpgradeBehavior - $this.Commands = $Converted.Commands - $this.Protocols = $Converted.Protocols - $this.FileExtensions = $Converted.FileExtensions - $this.Dependencies = $Converted.Dependencies - $this.PackageFamilyName = $Converted.PackageFamilyName - $this.ProductCode = $Converted.ProductCode - $this.Capabilities = $Converted.Capabilities - $this.RestricedCapabilities = $Converted.RestricedCapabilities - $this.MSStoreProductIdentifier = $Converted.MSStoreProductIdentifier - $this.InstallerAbortsTerminal = $Converted.InstallerAbortsTerminal - $this.ReleaseDate = $Converted.ReleaseDate - $this.InstallLocationRequired = $Converted.InstallLocationRequired - $this.RequireExplicitUpgrade = $Converted.RequireExplicitUpgrade - $this.ElevationRequirement = $Converted.ElevationRequirement - $this.UnsupportedOSArchitectures= $Converted.UnsupportedOSArchitectures - $this.AppsAndFeaturesEntries = $Converted.AppsAndFeaturesEntries - $this.Markets = $Converted.Markets - $this.DownloadCommandProhibited = $Converted.DownloadCommandProhibited - $this.RepairBehavior = $Converted.RepairBehavior - $this.ArchiveBinariesDependOnPath = $Converted.ArchiveBinariesDependOnPath - } - WinGetInstaller ([WinGetInstaller] $a) - { - $this.InstallerIdentifier = $a.InstallerIdentifier - $this.InstallerSha256 = $a.InstallerSha256 - $this.InstallerUrl = $a.InstallerUrl - $this.Architecture = $a.Architecture - $this.InstallerLocale = $a.InstallerLocale - $this.Platform = $a.Platform - $this.MinimumOsVersion = $a.MinimumOsVersion - $this.InstallerType = $a.InstallerType - $this.Scope = $a.Scope - $this.SignatureSha256 = $a.SignatureSha256 - $this.InstallModes = $a.InstallModes - $this.InstallerSwitches = $a.InstallerSwitches - $this.InstallerSuccessCodes = $a.InstallerSuccessCodes - $this.ExpectedReturnCodes = $a.ExpectedReturnCodes - $this.UpgradeBehavior = $a.UpgradeBehavior - $this.Commands = $a.Commands - $this.Protocols = $a.Protocols - $this.FileExtensions = $a.FileExtensions - $this.Dependencies = $a.Dependencies - $this.PackageFamilyName = $a.PackageFamilyName - $this.ProductCode = $a.ProductCode - $this.Capabilities = $a.Capabilities - $this.RestricedCapabilities = $a.RestricedCapabilities - $this.MSStoreProductIdentifier = $a.MSStoreProductIdentifier - $this.InstallerAbortsTerminal = $a.InstallerAbortsTerminal - $this.ReleaseDate = $a.ReleaseDate - $this.InstallLocationRequired = $a.InstallLocationRequired - $this.RequireExplicitUpgrade = $a.RequireExplicitUpgrade - $this.ElevationRequirement = $a.ElevationRequirement - $this.UnsupportedOSArchitectures= $a.UnsupportedOSArchitectures - $this.AppsAndFeaturesEntries = $a.AppsAndFeaturesEntries - $this.Markets = $a.Markets - $this.DownloadCommandProhibited = $a.DownloadCommandProhibited - $this.RepairBehavior = $a.RepairBehavior - $this.ArchiveBinariesDependOnPath = $a.ArchiveBinariesDependOnPath - } - WinGetInstaller ([psobject] $a) { - $this.InstallerIdentifier = $a.InstallerIdentifier - $this.InstallerSha256 = $a.InstallerSha256 - $this.InstallerUrl = $a.InstallerUrl - $this.Architecture = $a.Architecture - $this.InstallerLocale = $a.InstallerLocale - $this.Platform = $a.Platform - $this.MinimumOsVersion = $a.MinimumOsVersion - $this.InstallerType = $a.InstallerType - $this.Scope = $a.Scope - $this.SignatureSha256 = $a.SignatureSha256 - $this.InstallModes = $a.InstallModes - $this.InstallerSwitches = $a.InstallerSwitches - $this.InstallerSuccessCodes = $a.InstallerSuccessCodes - $this.ExpectedReturnCodes = $a.ExpectedReturnCodes - $this.UpgradeBehavior = $a.UpgradeBehavior - $this.Commands = $a.Commands - $this.Protocols = $a.Protocols - $this.FileExtensions = $a.FileExtensions - $this.Dependencies = $a.Dependencies - $this.PackageFamilyName = $a.PackageFamilyName - $this.ProductCode = $a.ProductCode - $this.Capabilities = $a.Capabilities - $this.RestricedCapabilities = $a.RestricedCapabilities - $this.MSStoreProductIdentifier = $a.MSStoreProductIdentifier - $this.InstallerAbortsTerminal = $a.InstallerAbortsTerminal - $this.ReleaseDate = $a.ReleaseDate - $this.InstallLocationRequired = $a.InstallLocationRequired - $this.RequireExplicitUpgrade = $a.RequireExplicitUpgrade - $this.ElevationRequirement = $a.ElevationRequirement - $this.UnsupportedOSArchitectures= $a.UnsupportedOSArchitectures - $this.AppsAndFeaturesEntries = $a.AppsAndFeaturesEntries - $this.Markets = $a.Markets - $this.DownloadCommandProhibited = $a.DownloadCommandProhibited - $this.RepairBehavior = $a.RepairBehavior - $this.ArchiveBinariesDependOnPath = $a.ArchiveBinariesDependOnPath - } - - [WinGetInstaller[]] Add ([WinGetInstaller] $a) - { - $FirstValue = [WinGetInstaller]::New($this) - $SecondValue = [WinGetInstaller]::New($a) - - [WinGetInstaller[]]$Result = @([WinGetInstaller]::New($FirstValue), [WinGetInstaller]::New($SecondValue)) - - Return $Result - } - - [WinGetInstaller[]] Add ([String[]]$a) - { - $FirstValue = [WinGetInstaller]::New($this) - $SecondValue = [WinGetInstaller]::New($a) - - [WinGetInstaller[]] $Combined = @([WinGetInstaller]::New($FirstValue), [WinGetInstaller]::New($SecondValue)) - - Return $Combined - } -} - -class WinGetVersion -{ - $PackageVersion - $Channel - [WinGetLocale] $DefaultLocale - [WinGetInstaller[]] $Installers - [WinGetLocale[]] $Locales - - WinGetVersion ([string] $a) - { - $Converted = $a | ConvertFrom-Json - - $this.PackageVersion = $Converted.PackageVersion - $this.Channel = $Converted.Channel - $this.DefaultLocale = $Converted.DefaultLocale - $this.Installers = $Converted.Installers - $this.Locales = $Converted.Locales - } - WinGetVersion ([WinGetVersion] $a) - { - $this.PackageVersion = $a.PackageVersion - $this.Channel = $a.Channel - $this.DefaultLocale = $a.DefaultLocale - $this.Installers = $a.Installers - $this.Locales = $a.Locales - } - WinGetVersion ([psobject] $a) { - $this.PackageVersion = $a.PackageVersion - $this.Channel = $a.Channel - $this.DefaultLocale = $a.DefaultLocale - $this.Installers = $a.Installers - $this.Locales = $a.Locales - } - - [WinGetVersion[]] Add ([WinGetVersion] $a) - { - $FirstValue = [WinGetVersion]::New($this) - $SecondValue = [WinGetVersion]::New($a) - - [WinGetVersion[]]$Result = @([WinGetVersion]::New($FirstValue), [WinGetVersion]::New($SecondValue)) - - Return $Result - } - - [WinGetVersion[]] Add ([String[]]$a) - { - $FirstValue = [WinGetVersion]::New($this) - $SecondValue = [WinGetVersion]::New($a) - - [WinGetVersion[]] $Combined = @([WinGetVersion]::New($FirstValue), [WinGetVersion]::New($SecondValue)) - - Return $Combined - } -} - -class WinGetManifest -{ - [string] $PackageIdentifier - [WinGetVersion[]] $Versions - - WinGetManifest ([string] $a) - { - Write-Verbose -Message "Creating a WinGetManifest object from String object" - - $Converted = $a | ConvertFrom-Json - $this.PackageIdentifier = $Converted.PackageIdentifier - $this.Versions = $Converted.Versions - } - WinGetManifest ([WinGetManifest] $a) - { - Write-Verbose -Message "Creating a WinGetManifest object from WinGetManifest object" - - $this.PackageIdentifier = $a.PackageIdentifier - $this.Versions = $a.Versions - } - WinGetManifest ([psobject] $a) { - Write-Verbose -Message "Creating a WinGetManifest object from PsObject object" - - $this.PackageIdentifier = $a.PackageIdentifier - $this.Versions = $a.Versions - } - - [WinGetManifest[]] Add ([WinGetManifest] $a) - { - $FirstValue = [WinGetManifest]::New($this) - $SecondValue = [WinGetManifest]::New($a) - - Write-Verbose -Message "Combining a WinGetManifest with the WinGetManifest object" - [WinGetManifest[]]$Result = @([WinGetManifest]::New($FirstValue), [WinGetManifest]::New($SecondValue)) - - Return $Result - } - - [WinGetManifest[]] Add ([String[]]$a) - { - $FirstValue = [WinGetManifest]::New($this) - $SecondValue = [WinGetManifest]::New($a) - - Write-Verbose -Message "Combining a String[] with the WinGetManifest object" - [WinGetManifest[]] $Combined = @([WinGetManifest]::New($FirstValue), [WinGetManifest]::New($SecondValue)) - - Return $Combined - } - - [WinGetManifest[]] Add ([psobject]$a) - { - $FirstValue = [WinGetManifest]::New($this) - $SecondValue = [WinGetManifest]::New($a) - - Write-Verbose -Message "Combining a PsObject with the WinGetManifest object" - [WinGetManifest[]] $Combined = @([WinGetManifest]::New($FirstValue), [WinGetManifest]::New($SecondValue)) - - Return $Combined - } - - [WinGetVersion[]] AddVersion ([WinGetVersion[]] $a) - { - $FirstValue = [WinGetVersion[]]::New($this.Versions) - $SecondValue = [WinGetVersion[]]::New($a) - - Write-Verbose -Message "Adding new Version to Manifest object" - [WinGetVersion[]] $Combined = @([WinGetVersion[]]::New($FirstValue), [WinGetVersion[]]::New($SecondValue)) - #$this.Versions = $Combined - - Return $Combined - } - - [string] GetJson () - { - [string]$Return = $this | ConvertTo-Json -Depth 8 - - ## Removes spacing from the JSON content - $Return = $($Return -replace "`t|`n|`r| ","") - $Return = $($($($($Return -replace ": ",":") -replace " { |{ ", "{") -replace ', ', ',') -replace " } | }", "}") - - Return $Return - } -} diff --git a/Tools/PowershellModule/src/Library/Convert-YAMLToJSON.ps1 b/Tools/PowershellModule/src/Library/Convert-YAMLToJSON.ps1 deleted file mode 100644 index a8b7662a..00000000 --- a/Tools/PowershellModule/src/Library/Convert-YAMLToJSON.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Function Convert-YamlToJson -{ - <# - .SYNOPSIS - Converts the YAML files in a specific directory to a PowerShell Object that can be exported to a JSON file. - - .DESCRIPTION - Converts the YAML files in a specific directory to a PowerShell Object that can be exported to a JSON file. - - .PARAMETER Path - Path to the directory containing YAML files. - - .EXAMPLE - Convert-YamlToJson -Path "C:\Folder\"" - - #> - - PARAM( - [Parameter(Position=0, Mandatory=$false)] [string]$Path - ) - BEGIN - { - $Files = Get-ChildItem $Path - $PackageManifest = [PackageManifest]::new() - } - PROCESS - { - $PackageManifest.ConvertFromYAML($Files) - } - END - { - Return $PackageManifest - } -} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 1b5b5547..9093864c 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -5,8 +5,8 @@ Function Get-WinGetManifest { <# .SYNOPSIS - Connects to the specified source REST API, or local file system path to retrieve the package manifests, returning - the manifest found. Allows for retrieving results based on the package identifier when targetting the REST APIs. + Connects to the specified source REST API, or local file system path to retrieve the package manifests, returning + the manifest found. Allows for retrieving results based on the package identifier when targeting the REST APIs. .DESCRIPTION Connects to the specified source REST API, or local file system path to retrieve the package Manifests, returning @@ -244,7 +244,7 @@ Function Get-WinGetManifest if($Result) { ## Sets the return result to be the contents of the JSON file if the Manifest test passed. - $Return = $ApplicationManifest + $Return = [WinGetManifest]::CreateFromString($ApplicationManifest) Write-Information "Returned Manifest from JSON file: $($Return.PackageIdentifier)" } diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 new file mode 100644 index 00000000..abdc4bce --- /dev/null +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -0,0 +1,121 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +class WinGetLocale +{ + $Moniker + $PackageLocale + $Publisher + $PublisherUrl + $PublisherSupportUrl + $PrivacyUrl + $Author + $PackageName + $PackageUrl + $License + $LicenseUrl + $Copyright + $CopyRightUrl + $ShortDescription + $Description + $ReleaseNotes + $ReleaseNotesUrl + #$Agreements + $Tags + + WinGetLocale () {} +} + +class WinGetInstaller +{ + $InstallerIdentifier + $InstallerSha256 + $InstallerUrl + $Architecture + $InstallerLocale + $Platform + $MinimumOsVersion + $InstallerType + $Scope + $SignatureSha256 + $InstallModes + $InstallerSwitches + $InstallerSuccessCodes + $ExpectedReturnCodes + $UpgradeBehavior + $Commands + $Protocols + $FileExtensions + $Dependencies + $PackageFamilyName + $ProductCode + $Capabilities + $RestricedCapabilities + $MSStoreProductIdentifier + $InstallerAbortsTerminal + $ReleaseDate + $InstallLocationRequired + $RequireExplicitUpgrade + $ElevationRequirement + $UnsupportedOSArchitectures + $AppsAndFeaturesEntries + #$Markets + $DownloadCommandProhibited + $RepairBehavior + $ArchiveBinariesDependOnPath + + WinGetInstaller () {} +} + +class WinGetVersion +{ + $PackageVersion + $Channel + [WinGetLocale] $DefaultLocale + [WinGetInstaller[]] $Installers + [WinGetLocale[]] $Locales + + WinGetVersion () {} +} + +class WinGetManifest +{ + [string] $PackageIdentifier + [WinGetVersion[]] $Versions + + WinGetManifest () {} + + [string] GetJson () + { + return [WinGetManifest]::SerializeJson($this) + } + + static [WinGetManifest] CreateFromString([string] $a) + { + Write-Verbose -Message "Creating a WinGetManifest object from String object" + + $options = [System.Text.Json.JsonSerializerOptions]::new() + + return [System.Text.Json.JsonSerializer]::Deserialize($a, [WinGetManifest], $options) + } + + static [WinGetManifest] CreateFromObject([psobject] $a) + { + Write-Verbose -Message "Creating a WinGetManifest object from PsObject object" + + $json = [WinGetManifest]::SerializeJson($a) + + return [WinGetManifest]::CreateFromString($json) + } + + static [string] SerializeJson ([psobject] $toSerialize) + { + $options = [System.Text.Json.JsonSerializerOptions]::new() + $options.WriteIndented = $false + $options.DefaultIgnoreCondition = [System.Text.Json.Serialization.JsonIgnoreCondition]::WhenWritingNull + $options.Converters.Add([System.Text.Json.Serialization.JsonStringEnumConverter]::new()) + $options.Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping + + Return [System.Text.Json.JsonSerializer]::Serialize($toSerialize, $options) + } +} diff --git a/Tools/PowershellModule/src/Library/WingetClasses.ps1 b/Tools/PowershellModule/src/Library/WingetClasses.ps1 deleted file mode 100644 index 14e829e6..00000000 --- a/Tools/PowershellModule/src/Library/WingetClasses.ps1 +++ /dev/null @@ -1,275 +0,0 @@ -class Locale -{ - [string]$Moniker = $null - [string]$PackageLocale = $null - [string]$Publisher = $null - [string]$PublisherUrl = $null - [string]$PublisherSupportUrl = $null - [string]$PrivacyUrl = $null - [string]$Author = $null - [string]$PackageName = $null - [string]$PackageUrl = $null - [string]$License = $null - [string]$LicenseUrl = $null - [string]$Copyright = $null - [string]$CopyrightUrl = $null - [string]$ShortDescription = $null - [string]$Description = $null - [System.Collections.ArrayList]$Tags = @() - - Locale () - {} - Locale ([Locale] $a) - { - $this = $a - } - Locale ($a) - { - $this.Moniker = $a.Moniker - $this.PackageLocale = $a.PackageLocale - $this.Publisher = $a.Publisher - $this.PublisherUrl = $a.PublisherUrl - $this.PublisherSupportUrl = $a.PublisherSupportUrl - $this.PrivacyUrl = $a.PrivacyUrl - $this.Author = $a.Author - $this.PackageName = $a.PackageName - $this.PackageUrl = $a.PackageUrl - $this.License = $a.License - $this.LicenseUrl = $a.LicenseUrl - $this.Copyright = $a.Copyright - $this.CopyrightUrl = $a.CopyRightUrl - $this.ShortDescription = $a.ShortDescription - $this.Description = $a.Description - - foreach ($Tag in $a.Tags){ - $this.Tags.add([string]::new($Tag)) - } - } -} - -class PackageDependencies -{ - [string]$PackageIdentifier = "" - [string]$MinimumVersion = "" - [System.Collections.ArrayList]$ExternalDependencies = @() - - Locale () - {} - Locale ([Locale] $a) - { - $this = $a - } - Locale ($a) - { - $this.PackageIdentifier = $a.PackageIdentifier - $this.MinimumVersion = $a.MinimumVersion - $this.ExternalDependencies = $a.ExternalDependencies - } -} - -class Dependencies -{ - [System.Collections.ArrayList]$WindowsFeatures = @() - [System.Collections.ArrayList]$WindowsLibraries = @() - [System.Collections.ArrayList]$PackageDependencies = @() - - Dependencies () - { - $this.WindowsFeatures.Add([string]::New("")) - $this.WindowsLibraries.Add([string]::New("")) - $this.PackageDependencies.Add([PackageDependencies]::New()) - } - Dependencies ([Dependencies] $a) - { - $this = $a - } - Dependencies ($a) - { - $this.WindowsFeatures = $a.WindowsFeatures - $this.WindowsLibraries = $a.WindowsLibraries - $this.PackageDependencies = $a.PackageDependencies - } -} - -class Installer -{ - $Architecture = $null - $InstallerIdentifier = $null - $InstallerSha256 = $null - $InstallerUrl = $null - $InstallerLocale = $null - [System.Collections.ArrayList]$Platform = @() - $MinimumOsVersion = $null - $InstallerType = $null - $Scope = $null - $SignatureSha256 = $null - [System.Collections.ArrayList]$InstallModes = @() - [System.Object]$InstallerSwitches = $null #Silent, SilentWithProgress, Interactive, InstallLocation, Log, Upgrade, Custom - [System.Collections.ArrayList]$InstallerSuccessCodes = @() - $UpgradeBehavior = "install" - [System.Collections.ArrayList]$Commands = @() - [System.Collections.ArrayList]$Protocols = @() - [System.Collections.ArrayList]$FileExtensions = @() - [System.Collections.ArrayList]$Dependencies = @() - $PackageFamilyName = $null - $ProductCode = $null - [System.Collections.ArrayList]$Capabilities = @() - [System.Collections.ArrayList]$RestrictedCapabilities = @() - [Boolean]$InstallerAbortsTerminal = $false - $ReleaseDate = "0001-01-01T00:00:00" - [Boolean]$InstallLocationRequired = $false - [Boolean]$RequireExplicitUpgrade = $false - [Boolean]$DisplayInstallWarnings = $false - [Boolean]$DownloadCommandProhibited = $false - - Installer () - {} - Installer ([Installer] $a) - { - $this = $a - } - Installer ($a) - { - $this.Architecture = $a.Architecture - $this.InstallerIdentifier = $a.InstallerIdentifier - $this.InstallerSha256 = $a.InstallerSha256 - $this.InstallerUrl = $a.InstallerUrl - $this.InstallerLocale = $a.InstallerLocale - $this.MinimumOsVersion = $a.MinimumOsVersion - $this.InstallerType = $a.InstallerType - $this.Scope = $a.Scope - $this.SignatureSha256 = $a.SignatureSha256 - $this.InstallModes = $a.InstallModes - $this.InstallerSwitches = $a.InstallerSwitches - $this.InstallerSuccessCodes = $a.InstallerSuccessCodes - $this.UpgradeBehavior = $a.UpgradeBehavior - $this.Commands = $a.Commands - $this.Protocols = $a.Protocols - $this.FileExtensions = $a.FileExtensions - $this.Dependencies = $a.Dependencies - $this.PackageFamilyName = $a.PackageFamilyName - $this.ProductCode = $a.ProductCode - $this.Capabilities = $a.Capabilities - $this.RestrictedCapabilities = $a.RestrictedCapabilities - - foreach ($platform in $a.Platform){ - $this.Platform.Add([string]::new($Platform)) - } - } -} - -class WingetVersion -{ - [string] $PackageVersion - [Locale] $DefaultLocale - [System.Collections.ArrayList]$Locales = @() - [System.Collections.ArrayList]$Installers = @() - - WingetVersion () - {} - WingetVersion ([WingetVersion] $a) - { - $this = $a - } - WingetVersion ($a) - { - $this.PackageVersion = $a.PackageVersion - } - AddInstaller ($a) - { - foreach ($Installer in $a.Installers) - { - $this.Installers.Add([Installer]::new($a)) - $i = $this.Installers.Count -1 - - $this.Installers[$i].Scope = $Installer.Scope - $this.Installers[$i].InstallerURL = $Installer.InstallerURL - $this.Installers[$i].InstallerSha256 = $Installer.InstallerSha256 - $this.Installers[$i].Architecture = $Installer.Architecture - $this.Installers[$i].InstallerIdentifier = "$($Installer.Architecture)-$($Installer.Scope)" - } - } - AddLocale ($a) - { - $i = $this.Locales.Count -1 - if($this.Locales[$i] -ne $([Locale]::new())){ - $this.Locales.Add([Locale]::new()) - } - - $i = $this.Locales.Count -1 - $this.locales[$i] = [Locale]::new($a) - } - AddDefaultLocale ($a) - { - $this.DefaultLocale = [Locale]::new($a) - } -} - -class PackageManifest -{ - $PackageIdentifier = "" - [System.Collections.ArrayList]$Versions = @() - [System.Collections.ArrayList]$ExternalDependencies = @() - - - PackageManifest () - { - } - PackageManifest ([PackageManifest] $a) - { - $this = $a - } - PackageManifest ($a) - { - $this.PackageIdentifier = $a.PackageIdentifier - $this.Versions = $a.Version - } - AddVersion ($a) - { - $this.Versions.Add([WingetVersion]::new($a)) - } - AddVersion ($a, $b, $c) - { - Write-Host $a - $this.Versions.Add([WingetVersion]::new($a)) - $i = $this.Versions.Count -1 - - foreach ($installer in $b) { - $this.Versions[$i].AddInstaller($installer) - } - foreach ($locale in $c) { - if ($locale.ManifestType -eq "defaultLocale") { - $this.Versions.AddDefaultLocale($locale) - } - else { - $this.Versions.AddLocale($locale) - } - } - } - ConvertFromYAML ($a) - { - [System.Collections.ArrayList]$YAMLInstallers = @() - [System.Collections.ArrayList]$YAMLLocales = @() - [System.Collections.ArrayList]$YAMLVersions = @() - - foreach ($file in $a){ - $FileContents = Get-Content -Raw -Path $file.FullName - - if ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.installer')) { - $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents - $YAMLInstallers.Add(($ConvertedContents)) - } - elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.defaultLocale')) { - $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents - $YAMLLocales.Add(($ConvertedContents)) - } - elseif ($FileContents.Contains('$schema=https://aka.ms/winget-manifest.version')) { - $ConvertedContents = ConvertFrom-Yaml -Yaml $FileContents - $YAMLVersions.Add(($ConvertedContents)) - } - } - - $this.PackageIdentifier = $YAMLVersions[0].PackageIdentifier - $this.AddVersion($YAMLVersions[0], $YAMLInstallers, $YAMLLocales) - } -} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index 8cf98ca1..3059c1d9 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -34,7 +34,7 @@ # RequiredModules = @('Az', 'powershell-yaml') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Convert-YamlToJson") + FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource") # Cmdlets to export from this module, for best performance, do not use wild cards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index 30448e71..83edb066 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -4,9 +4,6 @@ # Module script for module 'Microsoft.WinGet.Source' # -## Loads Libraries -Get-ChildItem -Path "$PSScriptRoot\Library" -Filter *.ps1 | foreach-object { . $_.FullName } - ## Loads the binaries from the Desktop App Installer Library - Only if running PowerShell at a specified edition try { Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" @@ -16,6 +13,12 @@ catch { throw $_ } +## Load classes first +. $PSScriptRoot\Library\WinGetManifest.ps1 + +## Loads Libraries +Get-ChildItem -Path "$PSScriptRoot\Library" -Filter *.ps1 | foreach-object { . $_.FullName } + ## Validates that the required Azure Modules are present when the script is imported. [string[]]$RequiredModules = @("Az", "powershell-yaml") diff --git a/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionExtended.cs b/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionExtended.cs index 010b1d69..f9e6b2bb 100644 --- a/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionExtended.cs +++ b/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionExtended.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -36,6 +36,7 @@ public VersionExtended(VersionExtended versionExtended) : base(versionExtended) { this.Installers = versionExtended.Installers; + this.Locales = versionExtended.Locales; } /// @@ -46,6 +47,7 @@ public VersionExtended(Version version) : base(version) { this.Installers = null; + this.Locales = null; } /// diff --git a/src/WinGet.RestSource.sln b/src/WinGet.RestSource.sln index 9488e1cc..2cd93498 100644 --- a/src/WinGet.RestSource.sln +++ b/src/WinGet.RestSource.sln @@ -74,8 +74,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{5DF7 ProjectSection(SolutionItems) = preProject ..\Tools\PowershellModule\src\Library\Add-AzureResourceGroup.ps1 = ..\Tools\PowershellModule\src\Library\Add-AzureResourceGroup.ps1 ..\Tools\PowershellModule\src\Library\Add-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Add-WinGetManifest.ps1 - ..\Tools\PowershellModule\src\Library\Classes.ps1 = ..\Tools\PowershellModule\src\Library\Classes.ps1 ..\Tools\PowershellModule\src\Library\Connect-ToAzure.ps1 = ..\Tools\PowershellModule\src\Library\Connect-ToAzure.ps1 + ..\Tools\PowershellModule\src\Library\Get-PairedAzureRegion.ps1 = ..\Tools\PowershellModule\src\Library\Get-PairedAzureRegion.ps1 ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 @@ -85,8 +85,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{5DF7 ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 = ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 = ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 = ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 - ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExists.ps1 = ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExists.ps1 + ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExist.ps1 = ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExist.ps1 ..\Tools\PowershellModule\src\Library\Test-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Test-WinGetManifest.ps1 + ..\Tools\PowershellModule\src\Library\WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\WinGetManifest.ps1 EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{5CA26277-F8CA-4136-81CD-1A3CDA3542A1}" From 2d4d5572e6fc679de1d9497e00a301d3ffa4eae0 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:42:18 -0800 Subject: [PATCH 04/16] net 8 --- .../src/Library/New-ARMObjects.ps1 | 16 ++++++++-------- .../src/Library/New-WinGetSource.ps1 | 5 ++--- .../src/Library/Test-ARMTemplate.ps1 | 3 +-- .../src/Library/Test-WinGetManifest.ps1 | 2 +- .../templates/restore-build-publish-test.yml | 2 +- .../Microsoft.WindowsPackageManager.Rest.csproj | 2 +- .../WinGet.RestSource.AppConfig.csproj | 2 +- .../WinGet.RestSource.Functions.csproj | 2 +- .../WinGet.RestSource.IntegrationTest.csproj | 2 +- .../WinGet.RestSource.PowershellSupport.csproj | 2 +- .../YamlToRestConverter.cs | 5 +++-- .../Tests/RestSource/Operation/UpdateTests.cs | 4 ++-- .../WinGet.RestSource.UnitTest.csproj | 2 +- .../Utils}/PackageManifestUtils.cs | 17 +---------------- .../WinGet.RestSource.Utils.csproj | 2 +- src/WinGet.RestSource/Operations/Rebuild.cs | 4 ++-- src/WinGet.RestSource/Operations/Update.cs | 4 ++-- src/WinGet.RestSource/WinGet.RestSource.csproj | 2 +- 18 files changed, 31 insertions(+), 47 deletions(-) rename src/{WinGet.RestSource.PowershellSupport/Helpers => WinGet.RestSource.Utils/Utils}/PackageManifestUtils.cs (92%) diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 2c501796..79d8c00c 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -22,18 +22,18 @@ Function New-ARMObjects .PARAMETER RestSourcePath Path to the compiled Function ZIP containing the REST APIs - .PARAMETER AzResourceGroup + .PARAMETER ResourceGroup Resource Group that will be used to create the ARM Objects in. .EXAMPLE - New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath "C:\WinGet-CLI-RestSource\WinGet.RestSource.Functions.zip" -AzResourceGroup "WinGet" + New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath "C:\WinGet-CLI-RestSource\WinGet.RestSource.Functions.zip" -ResourceGroup "WinGet" Parses through the $ARMObjects variable, creating all identified Azure Resources following the provided ARM Parameters and Template information. #> PARAM( - [Parameter(Position=0, Mandatory=$true)] $ARMObjects, + [Parameter(Position=0, Mandatory=$true)] [array] $ARMObjects, [Parameter(Position=1, Mandatory=$true)] [string] $RestSourcePath, - [Parameter(Position=2, Mandatory=$true)] [string] $AzResourceGroup + [Parameter(Position=2, Mandatory=$true)] [string] $ResourceGroup ) BEGIN { @@ -74,7 +74,7 @@ Function New-ARMObjects Write-Information -MessageData " Creating KeyVault Secrets:" ## Creates a reference to the Azure Storage Account Connection String as a Secret in the Azure Keyvault. - $AzStorageAccountKey = $(Get-AzStorageAccountKey -ResourceGroupName $AzResourceGroup -Name $AzStorageAccountName)[0].Value + $AzStorageAccountKey = $(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -Name $AzStorageAccountName)[0].Value ## Retrieves the required information from the previously created Azure objects. Values will be used to generate required information for the Azure Keyvault. ## [TODO:] Fix the secure string readings that were removed to unblock the 1ES pipeline migration. @@ -108,13 +108,13 @@ Function New-ARMObjects ## Create base object of the Azure Function, generating reference object ID for Keyvault Write-Verbose -Message " Creating base Azure Function object." Write-Information -MessageData " Creating base Azure Function object." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $AzResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue + $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue } ## Creates the Azure Resource Write-Verbose -Message " Creating $($Object.ObjectType) following the ARM Parameter File..." Write-Information -MessageData " Creating $($Object.ObjectType) following the ARM Parameter File..." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $AzResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue -ErrorVariable objerror -AsJob + $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue -ErrorVariable objerror -AsJob while ($Result.State -eq "Running") { ## Sets a sleep of 10 seconds after object creation to allow Azure to update creation status, and mark as "running" @@ -157,7 +157,7 @@ Function New-ARMObjects ## Uploads the Windows Package Manager functions to the Azure Function. Write-Verbose -Message " Copying function files to the Azure Function." Write-Information -MessageData " Copying function files to the Azure Function." - $Result = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $AzResourceGroup -Name $AzFunctionName -Force + $Result = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $AzFunctionName -Force } else { $ErrReturnObject = @{ diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index 5c98ba66..da3e2462 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -139,8 +139,7 @@ Function New-WinGetSource Result = $Result } - Write-Error -Message "Testing found an error with the ARM template or parameter files." -TargetObject $ErrReturnObject - Write-Host $Err[0] + Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject } @@ -156,7 +155,7 @@ Function New-WinGetSource ############################### ## Creates Azure Objects with ARM Templates and Parameters - New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -AzResourceGroup $ResourceGroup + New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup ############################### ## Shows how to connect local Windows Package Manager Client to newly created REST source diff --git a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 index 0b6b6183..b891154f 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 @@ -31,8 +31,7 @@ Function Test-ARMTemplate ) BEGIN { - Write-Information -MessageData "Verifying the ARM Resource Templates and Parameters are valid:" - Write-Verbose -Message "Verifying the ARM Resource Templates and Parameters are valid:" + Write-Verbose "Verifying the ARM Resource Templates and Parameters are valid:" $Return = @() } PROCESS diff --git a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 index 3211e828..87f17be3 100644 --- a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 @@ -26,7 +26,7 @@ Function Test-WinGetManifest #> [CmdletBinding(DefaultParameterSetName = 'File')] PARAM( - [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, + [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Object")] $Manifest ) BEGIN diff --git a/pipelines/templates/restore-build-publish-test.yml b/pipelines/templates/restore-build-publish-test.yml index 1a2f029e..e11f5720 100644 --- a/pipelines/templates/restore-build-publish-test.yml +++ b/pipelines/templates/restore-build-publish-test.yml @@ -61,5 +61,5 @@ steps: - template: run-unittests.yml@self parameters: name: WinGet.RestSource.UnitTest - testDirectory: '$(Build.SourcesDirectory)\src\WinGet.RestSource.UnitTest\bin\$(BuildConfiguration)\net6.0' + testDirectory: '$(Build.SourcesDirectory)\src\WinGet.RestSource.UnitTest\bin\$(BuildConfiguration)\net8.0' dll: Microsoft.WinGet.RestSource.UnitTest. diff --git a/src/Microsoft.WindowsPackageManager.Rest/Microsoft.WindowsPackageManager.Rest.csproj b/src/Microsoft.WindowsPackageManager.Rest/Microsoft.WindowsPackageManager.Rest.csproj index 7d48152e..509f7ccc 100644 --- a/src/Microsoft.WindowsPackageManager.Rest/Microsoft.WindowsPackageManager.Rest.csproj +++ b/src/Microsoft.WindowsPackageManager.Rest/Microsoft.WindowsPackageManager.Rest.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft true diff --git a/src/WinGet.RestSource.AppConfig/WinGet.RestSource.AppConfig.csproj b/src/WinGet.RestSource.AppConfig/WinGet.RestSource.AppConfig.csproj index 53299286..d897163e 100644 --- a/src/WinGet.RestSource.AppConfig/WinGet.RestSource.AppConfig.csproj +++ b/src/WinGet.RestSource.AppConfig/WinGet.RestSource.AppConfig.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft Microsoft.WinGet.RestSource.AppConfig diff --git a/src/WinGet.RestSource.Functions/WinGet.RestSource.Functions.csproj b/src/WinGet.RestSource.Functions/WinGet.RestSource.Functions.csproj index 90886424..fce5fa83 100644 --- a/src/WinGet.RestSource.Functions/WinGet.RestSource.Functions.csproj +++ b/src/WinGet.RestSource.Functions/WinGet.RestSource.Functions.csproj @@ -1,6 +1,6 @@ - net6.0 + net8.0 V4 Microsoft Microsoft diff --git a/src/WinGet.RestSource.IntegrationTest/WinGet.RestSource.IntegrationTest.csproj b/src/WinGet.RestSource.IntegrationTest/WinGet.RestSource.IntegrationTest.csproj index 90b6f39d..f53f2e93 100644 --- a/src/WinGet.RestSource.IntegrationTest/WinGet.RestSource.IntegrationTest.csproj +++ b/src/WinGet.RestSource.IntegrationTest/WinGet.RestSource.IntegrationTest.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft Microsoft.WinGet.RestSource.IntegrationTest diff --git a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj index f962c2c3..5811e19c 100644 --- a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj +++ b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft true diff --git a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs index 3625ca26..0b1fb2fb 100644 --- a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs +++ b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs @@ -9,7 +9,8 @@ namespace Microsoft.WinGet.RestSource.PowershellSupport using System; using System.IO; using System.Linq; - using Microsoft.WinGet.RestSource.PowershellSupport.Helpers; + using Microsoft.WinGet.RestSource.Utils; + using Microsoft.WinGet.RestSource.Utils.Common; using Microsoft.WinGet.RestSource.Utils.Models.Schemas; using Microsoft.WinGetUtil.Api; using Microsoft.WinGetUtil.Common; @@ -59,7 +60,7 @@ public static string AddManifestToPackageManifest( // Convert the manifest into a rest manifestPost format and merge with any existing data. PackageManifest packageManifest = PackageManifestUtils.AddManifestToPackageManifest( manifest, - priorRestManifest); + string.IsNullOrWhiteSpace(priorRestManifest) ? null : Parser.StringParser(priorRestManifest)); return JsonConvert.SerializeObject(packageManifest); } diff --git a/src/WinGet.RestSource.UnitTest/Tests/RestSource/Operation/UpdateTests.cs b/src/WinGet.RestSource.UnitTest/Tests/RestSource/Operation/UpdateTests.cs index 449821b7..9487cc3a 100644 --- a/src/WinGet.RestSource.UnitTest/Tests/RestSource/Operation/UpdateTests.cs +++ b/src/WinGet.RestSource.UnitTest/Tests/RestSource/Operation/UpdateTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -12,8 +12,8 @@ namespace Microsoft.Winget.RestSource.UnitTest.Tests.RestSource.Operation using Microsoft.WindowsPackageManager.Rest.Diagnostics; using Microsoft.WinGet.RestSource.Interfaces; using Microsoft.WinGet.RestSource.Operations; - using Microsoft.WinGet.RestSource.PowershellSupport.Helpers; using Microsoft.Winget.RestSource.UnitTest.Common; + using Microsoft.WinGet.RestSource.Utils; using Microsoft.WinGet.RestSource.Utils.Models.Schemas; using Microsoft.WinGetUtil.Models.V1; using Moq; diff --git a/src/WinGet.RestSource.UnitTest/WinGet.RestSource.UnitTest.csproj b/src/WinGet.RestSource.UnitTest/WinGet.RestSource.UnitTest.csproj index 89009b13..fdf752f6 100644 --- a/src/WinGet.RestSource.UnitTest/WinGet.RestSource.UnitTest.csproj +++ b/src/WinGet.RestSource.UnitTest/WinGet.RestSource.UnitTest.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft Microsoft.Winget.RestSource.UnitTest diff --git a/src/WinGet.RestSource.PowershellSupport/Helpers/PackageManifestUtils.cs b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs similarity index 92% rename from src/WinGet.RestSource.PowershellSupport/Helpers/PackageManifestUtils.cs rename to src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs index 142c057c..bea9995a 100644 --- a/src/WinGet.RestSource.PowershellSupport/Helpers/PackageManifestUtils.cs +++ b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs @@ -4,11 +4,10 @@ // // ----------------------------------------------------------------------- -namespace Microsoft.WinGet.RestSource.PowershellSupport.Helpers +namespace Microsoft.WinGet.RestSource.Utils { using System; using System.Collections.Generic; - using Microsoft.WinGet.RestSource.Utils.Common; using Microsoft.WinGet.RestSource.Utils.Models.Arrays; using Microsoft.WinGet.RestSource.Utils.Models.Core; using Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas; @@ -21,20 +20,6 @@ namespace Microsoft.WinGet.RestSource.PowershellSupport.Helpers /// public static class PackageManifestUtils { - /// - /// Merges a merged manifest object into an existing json representation of the app. - /// - /// Merged manifest object. - /// String package manifest. - /// A representing the rest source json representation of the package. - public static PackageManifest AddManifestToPackageManifest( - Manifest manifest, - string priorManifest) - { - PackageManifest priorPackageManifest = Parser.StringParser(priorManifest); - return AddManifestToPackageManifest(manifest, priorPackageManifest); - } - /// /// Merges a merged manifest object into an existing json representation of the app. /// diff --git a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj index 2afd5158..7caa2968 100644 --- a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj +++ b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft Microsoft.WinGet.RestSource.Utils diff --git a/src/WinGet.RestSource/Operations/Rebuild.cs b/src/WinGet.RestSource/Operations/Rebuild.cs index 10268287..d047fbff 100644 --- a/src/WinGet.RestSource/Operations/Rebuild.cs +++ b/src/WinGet.RestSource/Operations/Rebuild.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -18,8 +18,8 @@ namespace Microsoft.WinGet.RestSource.Operations using Microsoft.WindowsPackageManager.Rest.Utils; using Microsoft.WinGet.RestSource.Exceptions; using Microsoft.WinGet.RestSource.Interfaces; - using Microsoft.WinGet.RestSource.PowershellSupport.Helpers; using Microsoft.WinGet.RestSource.Sql; + using Microsoft.WinGet.RestSource.Utils; using Microsoft.WinGet.RestSource.Utils.Extensions; using Microsoft.WinGet.RestSource.Utils.Models.Schemas; using Microsoft.WinGetUtil.Models.V1; diff --git a/src/WinGet.RestSource/Operations/Update.cs b/src/WinGet.RestSource/Operations/Update.cs index 0c2fd826..c98d6ab0 100644 --- a/src/WinGet.RestSource/Operations/Update.cs +++ b/src/WinGet.RestSource/Operations/Update.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -14,7 +14,7 @@ namespace Microsoft.WinGet.RestSource.Operations using Microsoft.WindowsPackageManager.Rest.Models; using Microsoft.WindowsPackageManager.Rest.Utils; using Microsoft.WinGet.RestSource.Interfaces; - using Microsoft.WinGet.RestSource.PowershellSupport.Helpers; + using Microsoft.WinGet.RestSource.Utils; using Microsoft.WinGet.RestSource.Utils.Extensions; using Microsoft.WinGetUtil.Models.V1; diff --git a/src/WinGet.RestSource/WinGet.RestSource.csproj b/src/WinGet.RestSource/WinGet.RestSource.csproj index 21614470..a2055a7a 100644 --- a/src/WinGet.RestSource/WinGet.RestSource.csproj +++ b/src/WinGet.RestSource/WinGet.RestSource.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Microsoft Microsoft Microsoft.WinGet.RestSource From 439ce2515508f234de582ff3cf58b2adaabb774f Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:17:59 -0800 Subject: [PATCH 05/16] add and get done --- .../src/Library/Add-AzureResourceGroup.ps1 | 2 +- .../src/Library/Add-WinGetManifest.ps1 | 77 +++---- .../src/Library/Connect-ToAzure.ps1 | 51 ++--- .../src/Library/Get-WinGetManifest.ps1 | 12 +- .../src/Library/Remove-WinGetManifest.ps1 | 2 +- .../src/Library/Test-ARMTemplate.ps1 | 4 +- .../src/Library/WinGetManifest.ps1 | 198 ++++++++++++++---- .../Utils/PackageManifestUtils.cs | 90 -------- 8 files changed, 219 insertions(+), 217 deletions(-) diff --git a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 index b10ceb20..024a199a 100644 --- a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 +++ b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 @@ -49,7 +49,7 @@ Function Add-AzureResourceGroup ## Determines if the Resource Group already exists Write-Verbose "Retrieving details from Azure for the Resource Group Name $Name" - $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable err -InformationAction SilentlyContinue -WarningAction SilentlyContinue + $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable ErrorGet -InformationAction SilentlyContinue -WarningAction SilentlyContinue if(!$Result) { Write-Information "Failed to retrieve Resource Group, will attempt to create $Name in the specified $Region." diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index 31096335..c65b29c9 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -54,22 +54,10 @@ Function Add-WinGetManifest ) BEGIN { - ############################### - ## Validates that the Azure Modules are installed - $RequiredModules = @("Az.Resources", "Az.Accounts", "Az.Websites", "Az.Functions") - $Result = Test-PowerShellModuleExist -Modules $RequiredModules - $Response = @() - - $AzureFunctionName = $FunctionName - - if(!$Result) { - throw "Unable to run script, missing required PowerShell modules" - } - ############################### ## Connects to Azure, if not already connected. Write-Verbose -Message "Validating connection to azure, will attempt to connect if not already connected." - $Result = Connect-ToAzure + $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." } @@ -78,12 +66,12 @@ Function Add-WinGetManifest ## Determines the PowerShell Parameter Set that was used in the call of this Function. ## Sets variables as if the Azure Function Name was provided. Write-Verbose -Message "Determines the Azure Function Resource Group Name" - $AzureResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $AzureFunctionName}).ResourceGroupName + $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName ############################### ## Verify Azure Resources Exist - Write-Verbose -Message "Verifying that the Azure Resource $AzureFunctionName exists.." - $Result = Test-AzureResource -ResourceName $AzureFunctionName -ResourceGroup $AzureResourceGroupName + Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName if(!$Result) { throw "Failed to confirm resources exist in Azure. Please verify and try again." } @@ -93,31 +81,30 @@ Function Add-WinGetManifest Write-Verbose -Message "Retrieving a copy of the app Manifest file for submission to WinGet source." $ApplicationManifest = Get-WinGetManifest -Path $Path if(!$ApplicationManifest) { - Write-Verbose "$ApplicationManifest`n`n`n" throw "Failed to retrieve a proper manifest. Verify and try again." } - Write-Verbose -Message "Contents of the ($($ApplicationManifest.Count)) manifests have been retrieved [$ApplicationManifest]" + Write-Verbose -Message "Contents of ($($ApplicationManifest.Count)) manifests have been retrieved [$ApplicationManifest]" ############################################# ############## REST api call ############## ## Specifies the REST api call that will be performed Write-Verbose -Message "Setting the REST API Invoke Actions." - $apiContentType = "application/json" - $apiMethod = "Post" + $ApiContentType = "application/json" + $ApiMethod = "Post" ## Retrieves the Azure Function URL used to add new manifests to the REST source - Write-Verbose -Message "Retrieving the Azure Function $AzureFunctionName to build out the REST API request." - $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $AzureFunctionName -ErrorAction SilentlyContinue -ErrorVariable err + Write-Verbose -Message "Retrieving the Azure Function $FunctionName to build out the REST API request." + $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName ## can function key be part of the header $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName ## Creates the API Post Header - $apiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $apiHeader.Add("Accept", 'application/json') + $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $ApiHeader.Add("Accept", 'application/json') $AzFunctionURL = "https://" + $DefaultHostName + "/api/" + "packageManifests" } @@ -125,48 +112,38 @@ Function Add-WinGetManifest { foreach ($Manifest in $ApplicationManifest) { Write-Verbose -Message "Confirming that the Package ID doesn't already exist in Azure for $($Manifest.PackageIdentifier)." - $GetResult = Get-WinGetManifest -FunctionName $AzureFunctionName -SubscriptionName $SubscriptionName -PackageIdentifier $Manifest.PackageIdentifier + $GetResult = Get-WinGetManifest -FunctionName $FunctionName -SubscriptionName $SubscriptionName -PackageIdentifier $Manifest.PackageIdentifier - $ManifestObject = $Manifest - $TriggerName = "ManifestPost" - ## If the package already exists, return Error $GetResult | foreach-object { - Write-Verbose -Message "Reviewing the name found ""$($_.PackageIdentifier)"" matches with what we are looking to add ""$($ManifestObject.PackageIdentifier)""" - IF($_.PackageIdentifier -eq $ManifestObject.PackageIdentifier) { - $apiMethod = "Put" - $TriggerName = "VersionPost" + Write-Verbose -Message "Reviewing the name found ""$($_.PackageIdentifier)"" matches with what we are looking to add ""$($Manifest.PackageIdentifier)""" + IF($_.PackageIdentifier -eq $Manifest.PackageIdentifier) { + $ApiMethod = "Put" $AzFunctionURL += "/$($ApplicationManifest.PackageIdentifier)" $ApplicationManifest = Get-WinGetManifest -Path $Path -JSON $_ } } ## Determines the REST API that will be called, generates keys, and performs either Add (Post) or Update (Put) action. - Write-Verbose -Message "The Manifest will be added using the $apiMethod REST API." - $TriggerName = "Manifest$apiMethod" + Write-Verbose -Message "The Manifest will be added using the $ApiMethod REST API." + $TriggerName = "Manifest$ApiMethod" $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default - $apiHeader.Add("x-functions-key", $FunctionKey) + $ApiHeader.Add("x-functions-key", $FunctionKey) - $Response += Invoke-RestMethod $AzFunctionURL -Headers $apiHeader -Method $apiMethod -Body $ApplicationManifest.GetJson() -ContentType $apiContentType -ErrorVariable errInvoke + $Response += Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -Body $ApplicationManifest.GetJson() -ContentType $ApiContentType -ErrorVariable ErrorInvoke - if($errInvoke -ne @{}) { + if($ErrorInvoke) { $ErrReturnObject = @{ AzFunctionURL = $AzFunctionURL - apiHeader = $apiHeader - apiMethod = $apiMethod - apiContentType = $apiContentType + ApiHeader = $ApiHeader + ApiMethod = $ApiMethod + ApiContentType = $ApiContentType ApplicationManifest = $Manifest.GetJson() Response = $Response - InvokeError = $errInvoke + InvokeError = $ErrorInvoke } - ## If the Post failed, then return User specific error messages: - if($errInvoke -eq "Failure (409)"){ - Write-Warning -Message "Manifest file already exists." - } - else { - Write-Error -Message "Unhandled Error" -TargetObject $ErrReturnObject - } + Write-Error -Message "Failed to add manifest." -TargetObject $ErrReturnObject } } } @@ -176,9 +153,9 @@ Function Add-WinGetManifest if($Response.Data) { return $Response.Data } - ## If no new package is created, return a boolean: False + ## If no new package is created, return a $null else { - return $False + return $null } } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index 66b0cb32..85abaf33 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -46,65 +46,57 @@ Function Connect-ToAzure ) BEGIN { - $TestAzureConnection = Test-ConnectionToAzure - $Result = $true - $TestAzureSubscription = $true + $TestAzureConnection = $false - if($SubscriptionName -and $SubscriptionId -and $TestAzureConnection){ - ## If connected to Azure, and the Subscription Name and Id are provided then verify that the connected Azure session matches - ## to the provided Subscription Name and Id. + if($SubscriptionName -and $SubscriptionId){ + ## If connected to Azure, and the Subscription Name and Id are provided then verify that the connected Azure session matches the provided Subscription Name and Id. Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" - $TestAzureSubscription = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId - Write-Verbose -Message "Connection Result: $TestAzureSubscription" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId } - elseif($SubscriptionName -and $TestAzureConnection){ - ## If connected to Azure, and the Subscription Name are provided then verify that the connected Azure session matches to the - ## provided Subscription Name. + elseif($SubscriptionName){ + ## If connected to Azure, and the Subscription Name are provided then verify that the connected Azure session matches the provided Subscription Name. Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName" - $TestAzureSubscription = Test-ConnectionToAzure -SubscriptionName $SubscriptionName - Write-Verbose -Message "Connection Result: $TestAzureSubscription" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName } elseif($SubscriptionId -and $TestAzureConnection){ - ## If connected to Azure, and the Subscription Id are provided then verify that the connected Azure session matches to the - ## provided Subscription Id. + ## If connected to Azure, and the Subscription Id are provided then verify that the connected Azure session matches the provided Subscription Id. Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Id $SubscriptionId" - $TestAzureSubscription = Test-ConnectionToAzure -SubscriptionId $SubscriptionId - Write-Verbose -Message "Connection Result: $TestAzureSubscription" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId } else{ - Write-Verbose -Message "No Subscription Name or Subscription Id provided. Will test connection to default Azure Subscription" + Write-Information "No Subscription Name or Subscription Id provided. Will test connection to default Azure Subscription" + $TestAzureConnection = Test-ConnectionToAzure } + + Write-Verbose -Message "Test Connection Result: $TestAzureConnection" } PROCESS { - if(!$TestAzureConnection -or !$TestAzureSubscription) { - ## Not currently connected to Azure - Write-Verbose -Message "Not connected to Azure" - + if(!$TestAzureConnection) { if($SubscriptionName -and $SubscriptionId) { ## Attempts a connection to Azure using both the Subscription Name and Subscription Id - Write-Information -MessageData "Initiating a connection to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" + Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" Connect-AzAccount -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId } elseif($SubscriptionName) { - ## Attempts a connection to Azure using both the Subscription Name - Write-Information -MessageData "Initiating a connection to your Azure Subscription Name $SubscriptionName" + ## Attempts a connection to Azure using Subscription Name + Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName" Connect-AzAccount -SubscriptionName $SubscriptionName $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName } elseif($SubscriptionId) { - ## Attempts a connection to Azure using both the Subscription Id - Write-Information -MessageData "Initiating a connection to your Azure Subscription Id $SubscriptionId" + ## Attempts a connection to Azure using Subscription Id + Write-Information "Initiating a connection to your Azure Subscription Id $SubscriptionId" Connect-AzAccount -SubscriptionId $SubscriptionId $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId } else{ ## Attempts a connection to Azure with the users default Subscription - Write-Information -MessageData "Initiating a connection to your Azure environment." + Write-Information "Initiating a connection to your Azure environment." Connect-AzAccount $TestAzureConnection = Test-ConnectionToAzure @@ -119,12 +111,11 @@ Function Connect-ToAzure } Write-Error -Message "Failed to connect to Azure" -TargetObject $ErrReturnObject - $Result = $false } } } END { - return $Result + return $TestAzureConnection } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 9093864c..8e32be33 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -214,7 +214,7 @@ Function Get-WinGetManifest Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." ## Retrieves the Azure Function URL used to add new manifests to the REST source - $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName -ErrorAction SilentlyContinue -ErrorVariable err + $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName ## can function key be part of the header Write-Verbose -Message "Constructing the REST API call." @@ -223,7 +223,7 @@ Function Get-WinGetManifest $DefaultHostName = $FunctionApp.DefaultHostName $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default $ApiHeader.Add("x-functions-key", $FunctionKey) - $AzFunctionURL = "https://" + $DefaultHostName + "/api/" + "packageManifests" + $PackageIdentifier + $AzFunctionURL = "https://" + $DefaultHostName + "/api/packageManifests" + $PackageIdentifier ## Publishes the Manifest to the Windows Package Manager REST source Write-Verbose -Message "Invoking the REST API call." @@ -233,7 +233,7 @@ Function Get-WinGetManifest foreach ($Result in $Results.Data){ Write-Verbose -Message "Parsing through the returned results: $Result" - $Return += [WinGetManifest]::New($Result) + $Return += [WinGetManifest]::CreateFromObject($Result) } } "File" { @@ -244,7 +244,7 @@ Function Get-WinGetManifest if($Result) { ## Sets the return result to be the contents of the JSON file if the Manifest test passed. - $Return = [WinGetManifest]::CreateFromString($ApplicationManifest) + $Return += [WinGetManifest]::CreateFromString($ApplicationManifest) Write-Information "Returned Manifest from JSON file: $($Return.PackageIdentifier)" } @@ -255,11 +255,11 @@ Function Get-WinGetManifest Write-Verbose -Message "YAML Files have been found in the target directory. Building a JSON manifest with found files." if($Json){ Write-Verbose "Prior manifest provided. New manifest will be merged with prior manifest." - $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $JSON.GetJson()); + $Return += [WinGetManifest]::CreateFromString([Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $JSON.GetJson())) } else{ Write-Verbose "Prior manifest not provided." - $Return += [Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, ""); + $Return += [WinGetManifest]::CreateFromString([Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, "")) } Write-Information "Returned Manifest from YAML file: $($Return.PackageIdentifier)" diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index a3baf7d6..4fd535eb 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -84,7 +84,7 @@ Function Remove-WinGetManifest $apiContentType = "application/json" $apiMethod = "Delete" - $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName -ErrorAction SilentlyContinue -ErrorVariable err + $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName ## can function key be part of the header $FunctionAppId = $FunctionApp.Id diff --git a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 index b891154f..f373751b 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 @@ -26,8 +26,8 @@ Function Test-ARMTemplate #> PARAM( - [Parameter(Position=0, Mandatory=$true)] $ARMObjects, - [Parameter(Position=1, Mandatory=$true)] $ResourceGroup + [Parameter(Position=0, Mandatory=$true)] [array] $ARMObjects, + [Parameter(Position=1, Mandatory=$true)] [string] $ResourceGroup ) BEGIN { diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 index abdc4bce..414bf673 100644 --- a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -1,9 +1,36 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +class WinGetAgreement +{ + $AgreementLabel + $Agreement + $AgreementUrl + + WinGetAgreement () {} +} + +class WinGetDocumentation +{ + $DocumentLabel + $DocumentUrl + + WinGetDocumentation () {} +} + +class WinGetIcon +{ + $IconUrl + $IconFileType + $IconResolution + $IconTheme + $IconSha256 + + WinGetIcon () {} +} + class WinGetLocale { - $Moniker $PackageLocale $Publisher $PublisherUrl @@ -18,14 +45,109 @@ class WinGetLocale $CopyRightUrl $ShortDescription $Description + [string[]]$Tags $ReleaseNotes $ReleaseNotesUrl - #$Agreements - $Tags + [WinGetAgreement[]]$Agreements + $PurchaseUrl + $InstallationNotes + [WinGetDocument[]]$Documentations + [WinGetIcon[]]$Icons + + ## Default locale only + $Moniker WinGetLocale () {} } +class WinGetInstallerSwitch +{ + $Silent + $SilentWithProgress + $Interactive + $InstallLocation + $Log + $Upgrade + $Custom + $Repair + + WinGetInstallerSwitch () {} +} + +class WinGetExpectedReturnCode +{ + [long]$InstallerReturnCode + $ReturnResponse + $ReturnResponseUrl + + WinGetExpectedReturnCode () {} +} + +class WinGetPackageDependency +{ + $PackageIdentifier + $MinimumVersion + + WinGetPackageDependency () {} +} + +class WinGetDependencies +{ + [string[]]$WindowsFeatures + [string[]]$WindowsLibraries + [WinGetPackageDependency[]]$PackageDependencies + [string[]]$ExternalDependencies + + WinGetDependencies () {} +} + +class WinGetAppsAndFeaturesEntry +{ + $DisplayName + $Publisher + $DisplayVersion + $ProductCode + $UpgradeCode + $InstallerType + + WinGetAppsAndFeaturesEntry () {} +} + +class WinGetMarkets +{ + [string[]]$AllowedMarkets + [string[]]$ExcludedMarkets + + WinGetMarkets () {} +} + +class WinGetNestedInstallerFile +{ + $RelativeFilePath + $PortableCommandAlias + + WinGetNestedInstallerFile () {} +} + +class WinGetInstallationMetadataFile +{ + $RelativeFilePath + $FileSha256 + $FileType + $InvocationParameter + $DisplayName + + WinGetInstallationMetadataFile () {} +} + +class WinGetInstallationMetadata +{ + $DefaultInstallLocation + [WinGetInstallationMetadataFile[]]$Files + + WinGetInstallationMetadata () {} +} + class WinGetInstaller { $InstallerIdentifier @@ -33,36 +155,41 @@ class WinGetInstaller $InstallerUrl $Architecture $InstallerLocale - $Platform + [string[]]$Platform $MinimumOsVersion $InstallerType $Scope $SignatureSha256 - $InstallModes - $InstallerSwitches - $InstallerSuccessCodes - $ExpectedReturnCodes + [string[]]$InstallModes + [WinGetInstallerSwitch]$InstallerSwitches + [long[]]$InstallerSuccessCodes + [WinGetExpectedReturnCode[]]$ExpectedReturnCodes $UpgradeBehavior - $Commands - $Protocols - $FileExtensions - $Dependencies + [string[]]$Commands + [string[]]$Protocols + [string[]]$FileExtensions + [WinGetDependencies]$Dependencies $PackageFamilyName $ProductCode - $Capabilities - $RestricedCapabilities + [string[]]$Capabilities + [string[]]$RestricedCapabilities $MSStoreProductIdentifier - $InstallerAbortsTerminal + [bool]$InstallerAbortsTerminal $ReleaseDate - $InstallLocationRequired - $RequireExplicitUpgrade + [bool]$InstallLocationRequired + [bool]$RequireExplicitUpgrade $ElevationRequirement - $UnsupportedOSArchitectures - $AppsAndFeaturesEntries - #$Markets - $DownloadCommandProhibited + [string[]]$UnsupportedOSArchitectures + [WinGetAppsAndFeaturesEntry[]]$AppsAndFeaturesEntries + [WinGetMarkets]$Markets + $NestedInstallerType + [WinGetNestedInstallerFile[]]$NestedInstallerFiles + [bool]$DisplayInstallWarnings + [string[]]$UnsupportedArguments + [WinGetInstallationMetadata]$InstallationMetadata + [bool]$DownloadCommandProhibited $RepairBehavior - $ArchiveBinariesDependOnPath + [bool]$ArchiveBinariesDependOnPath WinGetInstaller () {} } @@ -87,10 +214,18 @@ class WinGetManifest [string] GetJson () { - return [WinGetManifest]::SerializeJson($this) + ## Not using ConvertTo-Json here since we want more control on null property handling + $options = [System.Text.Json.JsonSerializerOptions]::new() + $options.WriteIndented = $false + $options.MaxDepth = 16 + $options.DefaultIgnoreCondition = [System.Text.Json.Serialization.JsonIgnoreCondition]::WhenWritingNull + $options.Converters.Add([System.Text.Json.Serialization.JsonStringEnumConverter]::new()) + $options.Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping + + return [System.Text.Json.JsonSerializer]::Serialize($this, $options) } - static [WinGetManifest] CreateFromString([string] $a) + static [WinGetManifest] CreateFromString ([string] $a) { Write-Verbose -Message "Creating a WinGetManifest object from String object" @@ -99,23 +234,12 @@ class WinGetManifest return [System.Text.Json.JsonSerializer]::Deserialize($a, [WinGetManifest], $options) } - static [WinGetManifest] CreateFromObject([psobject] $a) + static [WinGetManifest] CreateFromObject ([psobject] $a) { Write-Verbose -Message "Creating a WinGetManifest object from PsObject object" - $json = [WinGetManifest]::SerializeJson($a) + $json = ConvertTo-Json $a -Depth 16 -Compress return [WinGetManifest]::CreateFromString($json) } - - static [string] SerializeJson ([psobject] $toSerialize) - { - $options = [System.Text.Json.JsonSerializerOptions]::new() - $options.WriteIndented = $false - $options.DefaultIgnoreCondition = [System.Text.Json.Serialization.JsonIgnoreCondition]::WhenWritingNull - $options.Converters.Add([System.Text.Json.Serialization.JsonStringEnumConverter]::new()) - $options.Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping - - Return [System.Text.Json.JsonSerializer]::Serialize($toSerialize, $options) - } } diff --git a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs index bea9995a..ef4896b5 100644 --- a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs +++ b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs @@ -44,96 +44,6 @@ public static PackageManifest AddManifestToPackageManifest( } // Create a new VersionExtended object and populate it with manifest info - // Fields are strings unless otherwise noted. - // PackageVersion - // Channel - // Default locale - // Moniker - // PackageLocale - // Publisher - // PublisherUrl - // PublisherSupportUrl - // PrivacyUrl - // Author - // PackageName - // PackageUrl - // License - // LicenseUrl - // Copyright - // CopyrightUrl - // ShortDescription - // Description - // Tags - // Locales - // Array of Locale where each contains: - // PackageLocale - // Publisher - // PublisherUrl - // PublisherSupportUrl - // PrivacyUrl - // Author - // PackageName - // PackageUrl - // License - // LicenseUrl - // Copyright - // CopyrightUrl - // ShortDescription - // Description - // Tags - // Installers - // Array of Installer where each contains: - // InstallerIdentifier - // InstallerSha256 - // InstallerUrl - // InstallerLocale - // Platform - // Array of strings - // MinimumOsVersion - // InstallerType - // Scope - // SignatureSha256 - // InstallModes - // Array of strings - // InstallerSwitches - // Silent - // SilentWithProgress - // Interactive - // InstallLocation - // Log - // Upgrade - // Custom - // Repair - // InstallerSuccessCodes - // Array of ints - // UpgradeBehavior - // Commands - // Array of strings - // Protocols - // Array of strings - // FileExtensions - // Array of strings - // Dependencies - // WindowsFeatures - // Array of strings - // WindowsLibraries - // Array of strings - // PackageDependencies - // Array of package dependencies where each contains: - // PackageIdentifier - // MinimumVersion - // ExternalDependencies - // Array of strings - // PackageFamilyName - // ProductCode - // Capabilities - // Array of strings - // RestrictedCapabilities - // Array of strings - // DownloadCommandProhibited - // bool - // RepairBehavior - // ArchiveBinariesDependOnPath VersionExtended versionExtended = new VersionExtended(); versionExtended.PackageVersion = manifest.Version; versionExtended.Channel = manifest.Channel; From e03fa1ed512245ff8e449ec0c4e949420ed90aa8 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:55:45 -0800 Subject: [PATCH 06/16] remove done --- .../src/Library/Add-AzureResourceGroup.ps1 | 3 - .../src/Library/Add-WinGetManifest.ps1 | 6 - .../src/Library/Connect-ToAzure.ps1 | 3 - .../src/Library/Get-WinGetManifest.ps1 | 6 - .../src/Library/New-ARMObjects.ps1 | 6 - .../src/Library/New-WinGetSource.ps1 | 6 - .../src/Library/Remove-WinGetManifest.ps1 | 159 ++++++++---------- .../src/Library/Test-ARMResourceName.ps1 | 6 - .../src/Library/Test-ConnectionToAzure.ps1 | 3 - .../src/Library/WinGetManifest.ps1 | 150 ++++++++--------- 10 files changed, 148 insertions(+), 200 deletions(-) diff --git a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 index 024a199a..fc66e5e4 100644 --- a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 +++ b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 @@ -11,9 +11,6 @@ Function Add-AzureResourceGroup the Resource Group doesn't exist, it will create a new Resource Group in Azure. If successful, returns a boolean. - True if the group was pre-existing or created successfully. - False if the group failed to be created. - - The following Azure Modules are used by this script: - Az.Resources --> Invoke-AzResourceAction .PARAMETER Name Name of the Resource Group to be created. diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index c65b29c9..47861c17 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -11,12 +11,6 @@ Function Add-WinGetManifest Windows Package Manager REST source, then collects the required URL for Manifest submission before retrieving the contents of the Package Manifest to submit. - The following Azure Modules are used by this script: - Az.Resources --> Invoke-AzResourceAction - Az.Accounts --> Connect-AzAccount, Get-AzContext - Az.Websites --> Get-AzWebapp - Az.Functions --> Get-AzFunctionApp - .PARAMETER FunctionName Name of the Azure Function that hosts the REST source. diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index 85abaf33..b54bc04e 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -9,9 +9,6 @@ Function Connect-ToAzure .DESCRIPTION By running this function the user will be prompted to conect to their Azure environment. If a connection is not already established to the (if specified) Subscription Name and / or Subscription Id. - - The following Azure Modules are used by this script: - Az.Accounts --> Connect-AzAccount, Get-AzContext .PARAMETER SubscriptionName [Optional] The Subscription name that contains the Windows Package Manager REST source REST APIs diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 8e32be33..4c39f965 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -11,12 +11,6 @@ Function Get-WinGetManifest .DESCRIPTION Connects to the specified source REST API, or local file system path to retrieve the package Manifests, returning an array of all Manifests found. Allows for retrieving results based on the package identifier. - - The following Azure Modules are used by this script: - Az.Resources --> Invoke-AzResourceAction - Az.Accounts --> Connect-AzAccount, Get-AzContext - Az.Websites --> Get-AzWebapp - Az.Functions --> Get-AzFunctionApp .PARAMETER Path Points to either a folder containing a specific application's manifest of type .json or .yaml or to a specific .json or .yaml file. diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 79d8c00c..196b0252 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -9,12 +9,6 @@ Function New-ARMObjects .DESCRIPTION Uses the custom PowerShell object provided by the "New-ARMParameterObject" cmdlet to create Azure resources, and will create the the Key Vault secrets and publish the Windows Package Manager REST source REST apis to the Azure Function. - - The following Azure Modules are used by this script: - Az.Resources - Az.Accounts - Az.Websites - Az.Functions .PARAMETER ARMObjects Object returned from the "New-ARMParameterObject" providing the paths to the ARM Parameters and Template files. diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index da3e2462..4296c640 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -9,12 +9,6 @@ Function New-WinGetSource .DESCRIPTION Creates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. - The following Azure Modules are used by this script: - Az.Resources - Az.Accounts - Az.Websites - Az.Functions - .PARAMETER Name The name of the objects that will be created diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index 4fd535eb..873ff65d 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -9,21 +9,15 @@ Function Remove-WinGetManifest .DESCRIPTION This function will connect to the Azure Tenant that hosts the Windows Package Manager REST source, removing the specified package Manifest. - - The following Azure Modules are used by this script: - Az.Resources - Az.Accounts - Az.Websites - Az.Functions .PARAMETER FunctionName Name of the Azure Function that hosts the REST source. .PARAMETER PackageIdentifier - THe Package Id that represents the App Manifest to be removed. + The Package Id that represents the App Manifest to be removed. .PARAMETER SubscriptionName - [Optional] The Subscription name contains the Windows Package Manager REST source + [Optional] The Subscription name that contains the Windows Package Manager REST source .EXAMPLE Remove-WinGetManifest -FunctionName "contosorestsource" -PackageIdentifier "Windows.PowerToys" @@ -32,98 +26,91 @@ Function Remove-WinGetManifest the Windows Package Manager REST source #> - [CmdletBinding(DefaultParameterSetName = 'WinGet')] PARAM( - [Parameter(Position=0, Mandatory=$true, ParameterSetName="Azure")] [string]$FunctionName, + [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, [Parameter(Position=2, Mandatory=$true)] [string]$PackageIdentifier, - [Parameter(Position=3, Mandatory=$false)] [string]$SubscriptionName = "" + [Parameter(Position=2, Mandatory=$false)] [string]$PackageVersion = "", + [Parameter(Position=3, Mandatory=$false)] [string]$SubscriptionName = "" ) BEGIN { - $Found = $false - - switch ($PsCmdlet.ParameterSetName) { - "Azure" { - ############################### - ## Validates that the Azure Modules are installed - Write-Verbose -Message "Testing required PowerShell Modules are installed." - $RequiredModules = @("Az.Resources", "Az.Accounts", "Az.Websites", "Az.Functions") - $Result = Test-PowerShellModuleExist -Modules $RequiredModules - - if(!$Result) { - throw "Unable to run script, missing required PowerShell modules" - } - - ############################### - ## Connects to Azure, if not already connected. - Write-Verbose -Message "Testing connection to Azure." - $Result = Connect-ToAzure -SubscriptionName $SubscriptionName - if(!($Result)) { - throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." - } - - ## Sets variables as if the Azure Function Name was provided. - $AzureResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName - - ############################### - ## Verify Azure Resources Exist - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $AzureResourceGroupName - if(!$Result) { - throw "Failed to confirm resources exist in Azure. Please verify and try again." - } - - if($PackageIdentifier){ - $PackageIdentifier = "$PackageIdentifier" - } + ############################### + ## Connects to Azure, if not already connected. + Write-Verbose -Message "Testing connection to Azure." + $Result = Connect-ToAzure -SubscriptionName $SubscriptionName + if(!($Result)) { + throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + } + + ## Sets variables as if the Azure Function Name was provided. + $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName + + ############################### + ## Verify Azure Resources Exist + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName + if(!$Result) { + throw "Failed to confirm resources exist in Azure. Please verify and try again." + } + + if($PackageIdentifier){ + $PackageIdentifier = "$PackageIdentifier" + } - ############################### - ## REST api call - - ## Specifies the REST api call that will be performed - $TriggerName = "ManifestDelete" - $apiContentType = "application/json" - $apiMethod = "Delete" - - $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName + ############################### + ## REST api call - ## can function key be part of the header - $FunctionAppId = $FunctionApp.Id - $DefaultHostName = $FunctionApp.DefaultHostName - $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default + ## Specifies the REST api call that will be performed + $TriggerName = "" + if([string]::IsNullOrWhiteSpace($PackageVersion)) { + $TriggerName = "ManifestDelete" + } + else { + $TriggerName = "VersionDelete" + } - ## Creates the API Post Header - $apiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $apiHeader.Add("Accept", 'application/json') - $apiHeader.Add("x-functions-key", $FunctionKey) + $ApiContentType = "application/json" + $ApiMethod = "Delete" - $AzFunctionURL = "https://" + $DefaultHostName + "/api/" + "packageManifests/" + $PackageIdentifier - } + $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + + ## can function key be part of the header + $FunctionAppId = $FunctionApp.Id + $DefaultHostName = $FunctionApp.DefaultHostName + $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default + + ## Creates the API Post Header + $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $ApiHeader.Add("Accept", 'application/json') + $ApiHeader.Add("x-functions-key", $FunctionKey) + + $AzFunctionURL = "" + if([string]::IsNullOrWhiteSpace($PackageVersion)) { + $AzFunctionURL = "https://" + $DefaultHostName + "/api/packageManifests/" + $PackageIdentifier + } + else { + $AzFunctionURL = "https://" + $DefaultHostName + "/api/packages/" + $PackageIdentifier + "/versions/" + $PackageVersion } } PROCESS { - switch ($PsCmdlet.ParameterSetName) { - "Azure" { - Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." - Write-Verbose -Message "Constructing the REST API call for removal of manifest." - - $Response = Invoke-RestMethod $AzFunctionURL -Headers $apiHeader -Method $apiMethod -ContentType $apiContentType -ErrorVariable errInvoke - - if($errInvoke -ne $()) { - $ErrorMessage = "Failed to remove Manifest from $FunctionName. Verify the information you provided and try again." - $ErrReturnObject = @{ - AzFunctionURL = $AzFunctionURL - apiHeader = $apiHeader - apiMethod = $apiMethod - apiContentType = $apiContentType - Response = $Response - InvokeError = $errInvoke - } - - ## If the Post failed, then return User specific error messages: - Write-Error -Message $ErrorMessage -Category ResourceUnavailable -TargetObject $ErrReturnObject - } + Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." + Write-Verbose -Message "Constructing the REST API call for removal of manifest." + + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType -ErrorVariable ErrorInvoke + + if($ErrorInvoke) { + $ErrorMessage = "Failed to remove Manifest from $FunctionName. Verify the information you provided and try again." + $ErrReturnObject = @{ + AzFunctionURL = $AzFunctionURL + ApiHeader = $ApiHeader + ApiMethod = $ApiMethod + ApiContentType = $ApiContentType + Response = $Response + InvokeError = $ErrorInvoke } + + ## If the Post failed, then return User specific error messages: + Write-Error -Message $ErrorMessage -Category ResourceUnavailable -TargetObject $ErrReturnObject } } END diff --git a/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 b/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 index 3054efb8..41fe7b3f 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 @@ -8,12 +8,6 @@ Function Test-ARMResourceName .DESCRIPTION Validates that the name that will be assigned to the Azure Resource will meet the resource types requirement. - - The following Azure Modules are used by this script: - Az.Resources - Az.Accounts - Az.Websites - Az.Functions .PARAMETER ResourceType The type of Azure Resource to validate requirements against. diff --git a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 index 26f3c622..f86244d8 100644 --- a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 @@ -9,9 +9,6 @@ Function Test-ConnectionToAzure .DESCRIPTION Validates that a connection is existing to Azure and/or Azure Subscription Name / Id. - The following Azure Modules are used by this script: - Az.Accounts - .PARAMETER SubscriptionName [Optional] Name of the Azure Subscription. diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 index 414bf673..6d8a9af3 100644 --- a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -3,73 +3,73 @@ class WinGetAgreement { - $AgreementLabel - $Agreement - $AgreementUrl + [string]$AgreementLabel + [string]$Agreement + [string]$AgreementUrl WinGetAgreement () {} } class WinGetDocumentation { - $DocumentLabel - $DocumentUrl + [string]$DocumentLabel + [string]$DocumentUrl WinGetDocumentation () {} } class WinGetIcon { - $IconUrl - $IconFileType - $IconResolution - $IconTheme - $IconSha256 + [string]$IconUrl + [string]$IconFileType + [string]$IconResolution + [string]$IconTheme + [string]$IconSha256 WinGetIcon () {} } class WinGetLocale { - $PackageLocale - $Publisher - $PublisherUrl - $PublisherSupportUrl - $PrivacyUrl - $Author - $PackageName - $PackageUrl - $License - $LicenseUrl - $Copyright - $CopyRightUrl - $ShortDescription - $Description + [string]$PackageLocale + [string]$Publisher + [string]$PublisherUrl + [string]$PublisherSupportUrl + [string]$PrivacyUrl + [string]$Author + [string]$PackageName + [string]$PackageUrl + [string]$License + [string]$LicenseUrl + [string]$Copyright + [string]$CopyRightUrl + [string]$ShortDescription + [string]$Description [string[]]$Tags - $ReleaseNotes - $ReleaseNotesUrl + [string]$ReleaseNotes + [string]$ReleaseNotesUrl [WinGetAgreement[]]$Agreements - $PurchaseUrl - $InstallationNotes - [WinGetDocument[]]$Documentations + [string]$PurchaseUrl + [string]$InstallationNotes + [WinGetDocumentation[]]$Documentations [WinGetIcon[]]$Icons ## Default locale only - $Moniker + [string]$Moniker WinGetLocale () {} } class WinGetInstallerSwitch { - $Silent - $SilentWithProgress - $Interactive - $InstallLocation - $Log - $Upgrade - $Custom - $Repair + [string]$Silent + [string]$SilentWithProgress + [string]$Interactive + [string]$InstallLocation + [string]$Log + [string]$Upgrade + [string]$Custom + [string]$Repair WinGetInstallerSwitch () {} } @@ -77,16 +77,16 @@ class WinGetInstallerSwitch class WinGetExpectedReturnCode { [long]$InstallerReturnCode - $ReturnResponse - $ReturnResponseUrl + [string]$ReturnResponse + [string]$ReturnResponseUrl WinGetExpectedReturnCode () {} } class WinGetPackageDependency { - $PackageIdentifier - $MinimumVersion + [string]$PackageIdentifier + [string]$MinimumVersion WinGetPackageDependency () {} } @@ -103,12 +103,12 @@ class WinGetDependencies class WinGetAppsAndFeaturesEntry { - $DisplayName - $Publisher - $DisplayVersion - $ProductCode - $UpgradeCode - $InstallerType + [string]$DisplayName + [string]$Publisher + [string]$DisplayVersion + [string]$ProductCode + [string]$UpgradeCode + [string]$InstallerType WinGetAppsAndFeaturesEntry () {} } @@ -123,26 +123,26 @@ class WinGetMarkets class WinGetNestedInstallerFile { - $RelativeFilePath - $PortableCommandAlias + [string]$RelativeFilePath + [string]$PortableCommandAlias WinGetNestedInstallerFile () {} } class WinGetInstallationMetadataFile { - $RelativeFilePath - $FileSha256 - $FileType - $InvocationParameter - $DisplayName + [string]$RelativeFilePath + [string]$FileSha256 + [string]$FileType + [string]$InvocationParameter + [string]$DisplayName WinGetInstallationMetadataFile () {} } class WinGetInstallationMetadata { - $DefaultInstallLocation + [string]$DefaultInstallLocation [WinGetInstallationMetadataFile[]]$Files WinGetInstallationMetadata () {} @@ -150,45 +150,45 @@ class WinGetInstallationMetadata class WinGetInstaller { - $InstallerIdentifier - $InstallerSha256 - $InstallerUrl - $Architecture - $InstallerLocale + [string]$InstallerIdentifier + [string]$InstallerSha256 + [string]$InstallerUrl + [string]$Architecture + [string]$InstallerLocale [string[]]$Platform - $MinimumOsVersion - $InstallerType - $Scope - $SignatureSha256 + [string]$MinimumOsVersion + [string]$InstallerType + [string]$Scope + [string]$SignatureSha256 [string[]]$InstallModes [WinGetInstallerSwitch]$InstallerSwitches [long[]]$InstallerSuccessCodes [WinGetExpectedReturnCode[]]$ExpectedReturnCodes - $UpgradeBehavior + [string]$UpgradeBehavior [string[]]$Commands [string[]]$Protocols [string[]]$FileExtensions [WinGetDependencies]$Dependencies - $PackageFamilyName - $ProductCode + [string]$PackageFamilyName + [string]$ProductCode [string[]]$Capabilities [string[]]$RestricedCapabilities - $MSStoreProductIdentifier + [string]$MSStoreProductIdentifier [bool]$InstallerAbortsTerminal - $ReleaseDate + [string]$ReleaseDate [bool]$InstallLocationRequired [bool]$RequireExplicitUpgrade - $ElevationRequirement + [string]$ElevationRequirement [string[]]$UnsupportedOSArchitectures [WinGetAppsAndFeaturesEntry[]]$AppsAndFeaturesEntries [WinGetMarkets]$Markets - $NestedInstallerType + [string]$NestedInstallerType [WinGetNestedInstallerFile[]]$NestedInstallerFiles [bool]$DisplayInstallWarnings [string[]]$UnsupportedArguments [WinGetInstallationMetadata]$InstallationMetadata [bool]$DownloadCommandProhibited - $RepairBehavior + [string]$RepairBehavior [bool]$ArchiveBinariesDependOnPath WinGetInstaller () {} @@ -196,8 +196,8 @@ class WinGetInstaller class WinGetVersion { - $PackageVersion - $Channel + [string]$PackageVersion + [string]$Channel [WinGetLocale] $DefaultLocale [WinGetInstaller[]] $Installers [WinGetLocale[]] $Locales From 74775d531a0bd8556e4ffd8ca689b8d7e7fe9188 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 29 Nov 2024 01:43:27 -0800 Subject: [PATCH 07/16] pipeline support --- .../src/Library/Add-AzureResourceGroup.ps1 | 62 ++- .../src/Library/Add-WinGetManifest.ps1 | 143 +++--- .../src/Library/Connect-ToAzure.ps1 | 114 +++-- .../src/Library/Get-PairedAzureRegion.ps1 | 429 +++++++++--------- .../src/Library/Get-WinGetManifest.ps1 | 170 ++++--- .../src/Library/New-ARMObjects.ps1 | 36 +- .../src/Library/New-ARMParameterObject.ps1 | 4 +- .../src/Library/New-WinGetSource.ps1 | 83 ++-- .../src/Library/Remove-WinGetManifest.ps1 | 79 ++-- .../src/Library/Test-ARMResourceName.ps1 | 211 --------- .../src/Library/Test-ARMTemplate.ps1 | 74 +-- .../src/Library/Test-AzureResource.ps1 | 111 +++-- .../src/Library/Test-ConnectionToAzure.ps1 | 50 +- .../Library/Test-PowerShellModuleExist.ps1 | 56 +-- .../src/Library/Test-WinGetManifest.ps1 | 74 ++- .../src/Library/WinGetManifest.ps1 | 2 + .../src/Microsoft.WinGet.Source.psd1 | 2 +- .../src/Microsoft.WinGet.Source.psm1 | 2 +- .../YamlToRestConverter.cs | 44 +- src/WinGet.RestSource.sln | 1 - .../Cosmos/CosmosDataStore.cs | 6 +- 21 files changed, 738 insertions(+), 1015 deletions(-) delete mode 100644 Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 diff --git a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 index fc66e5e4..dd29c27f 100644 --- a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 +++ b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 @@ -28,46 +28,40 @@ Function Add-AzureResourceGroup [Parameter(Position=0, Mandatory=$true)] [string]$Name, [Parameter(Position=1, Mandatory=$true)] [string]$Region ) - BEGIN - { - $Return = $false - - ## Normalize resource group name - $NormalizedName = $Name -replace "[^a-zA-Z0-9-()_.]", "" - if($Name -cne $NormalizedName) { - $Name = $NormalizedName - Write-Warning "Removed special characters from the Azure Resource Group Name (New Name: $Name)." - } + + $Return = $false + + ## Normalize resource group name + $NormalizedName = $Name -replace "[^a-zA-Z0-9-()_.]", "" + if($Name -cne $NormalizedName) { + $Name = $NormalizedName + Write-Warning "Removed special characters from the Azure Resource Group Name (New Name: $Name)." } - PROCESS - { - ## Creating a line break from previous steps - Write-Verbose "Azure Resource Group Name to be created: $Name" + + ## Creating a line break from previous steps + Write-Verbose "Azure Resource Group Name to be created: $Name" - ## Determines if the Resource Group already exists - Write-Verbose "Retrieving details from Azure for the Resource Group Name $Name" - $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable ErrorGet -InformationAction SilentlyContinue -WarningAction SilentlyContinue + ## Determines if the Resource Group already exists + Write-Verbose "Retrieving details from Azure for the Resource Group name $Name" + $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable ErrorGet -InformationAction SilentlyContinue -WarningAction SilentlyContinue - if(!$Result) { - Write-Information "Failed to retrieve Resource Group, will attempt to create $Name in the specified $Region." - - $Result = New-AzResourceGroup -Name $Name -Location $Region - if($Result) { - Write-Information "Resource Group $Name has been created in the $Region region." - $Return = $true - } - else { - Write-Error "Failed to retrieve or create Resource Group with name $Name." - } + if(!$Result) { + Write-Information "Failed to retrieve Resource Group, will attempt to create $Name in the specified $Region." + + $Result = New-AzResourceGroup -Name $Name -Location $Region + if($Result) { + Write-Information "Resource Group $Name has been created in the $Region region." + $Return = $true } else { - ## Found an existing Resource Group matching the name of $Name - Write-Warning "Found an existing Resource Group matching the name of $Name. Will not create a new Resource Group." - $Return = $true + Write-Error "Failed to retrieve or create Resource Group with name $Name." } } - END - { - Return $Return + else { + ## Found an existing Resource Group matching the name of $Name + Write-Warning "Found an existing Resource Group matching the name of $Name. Will not create a new Resource Group." + $Return = $true } + + return $Return } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index 47861c17..77429f48 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -15,7 +15,7 @@ Function Add-WinGetManifest Name of the Azure Function that hosts the REST source. .PARAMETER Path - The path to the Package Manifest file or folder hosting either a JSON or YAML file(s) that will be uploaded to the REST source. + Supports input from pipeline. The path to the Package Manifest file or folder hosting either a JSON or YAML file(s) that will be uploaded to the REST source. This path may contain a single Package Manifest file, or a folder containing files for a single Package Manifest. Does not support targeting a single folder of multiple different applications. @@ -43,113 +43,136 @@ Function Add-WinGetManifest PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, - [Parameter(Position=1, Mandatory=$true)] [string]$Path, + [Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$true)] [string]$Path, [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "" ) BEGIN { + [WinGetManifest[]] $Return = @() + ############################### ## Connects to Azure, if not already connected. Write-Verbose -Message "Validating connection to azure, will attempt to connect if not already connected." $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { - throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." -ErrorAction Stop } ############################### - ## Determines the PowerShell Parameter Set that was used in the call of this Function. - ## Sets variables as if the Azure Function Name was provided. + ## Gets Resource Group name of the Azure Function Write-Verbose -Message "Determines the Azure Function Resource Group Name" $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName + if(!$ResourceGroupName) { + Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop + } ############################### ## Verify Azure Resources Exist Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName if(!$Result) { - throw "Failed to confirm resources exist in Azure. Please verify and try again." - } - - ############################### - ## Gets the content from the Package Manifest (*.JSON, or *.YAML) file for posting to REST source. - Write-Verbose -Message "Retrieving a copy of the app Manifest file for submission to WinGet source." - $ApplicationManifest = Get-WinGetManifest -Path $Path - if(!$ApplicationManifest) { - throw "Failed to retrieve a proper manifest. Verify and try again." + Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop } - Write-Verbose -Message "Contents of ($($ApplicationManifest.Count)) manifests have been retrieved [$ApplicationManifest]" - ############################################# ############## REST api call ############## ## Specifies the REST api call that will be performed - Write-Verbose -Message "Setting the REST API Invoke Actions." $ApiContentType = "application/json" - $ApiMethod = "Post" + $ApiMethodPost = "Post" + $ApiMethodGet = "Get" + $ApiMethodPut = "Put" ## Retrieves the Azure Function URL used to add new manifests to the REST source Write-Verbose -Message "Retrieving the Azure Function $FunctionName to build out the REST API request." $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName - ## can function key be part of the header $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName + $FunctionKeyPost = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/ManifestPost" -Action listkeys -Force).default + $FunctionKeyGet = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/ManifestGet" -Action listkeys -Force).default + $FunctionKeyPut = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/ManifestPut" -Action listkeys -Force).default + ## Creates the API Post Header $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $ApiHeader.Add("Accept", 'application/json') - $AzFunctionURL = "https://" + $DefaultHostName + "/api/" + "packageManifests" + $AzFunctionURLBase = "https://" + $DefaultHostName + "/api/packageManifests/" } PROCESS { - foreach ($Manifest in $ApplicationManifest) { - Write-Verbose -Message "Confirming that the Package ID doesn't already exist in Azure for $($Manifest.PackageIdentifier)." - $GetResult = Get-WinGetManifest -FunctionName $FunctionName -SubscriptionName $SubscriptionName -PackageIdentifier $Manifest.PackageIdentifier - - ## If the package already exists, return Error - $GetResult | foreach-object { - Write-Verbose -Message "Reviewing the name found ""$($_.PackageIdentifier)"" matches with what we are looking to add ""$($Manifest.PackageIdentifier)""" - IF($_.PackageIdentifier -eq $Manifest.PackageIdentifier) { - $ApiMethod = "Put" - $AzFunctionURL += "/$($ApplicationManifest.PackageIdentifier)" - $ApplicationManifest = Get-WinGetManifest -Path $Path -JSON $_ - } + ############################### + ## Gets the content from the Package Manifest (*.JSON, or *.YAML) file for posting to REST source. + Write-Verbose -Message "Retrieving a copy of the app Manifest file for submission to WinGet source." + $ApplicationManifest = Get-WinGetManifest -Path $Path + if($ApplicationManifest.Count -ne 1) { + Write-Error "Failed to retrieve a proper manifest. Verify and try again." + return + } + + $Manifest = $ApplicationManifest[0] + Write-Verbose -Message "Contents of manifest have been retrieved. Package Identifier: $($Manifest.PackageIdentifier)." + + Write-Verbose -Message "Confirming that the Package ID doesn't already exist in Azure for $($Manifest.PackageIdentifier)." + $ApiHeader.Remove("x-functions-key") + $ApiHeader.Add("x-functions-key", $FunctionKeyGet) + $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethodGet -ContentType $ApiContentType -ErrorVariable ErrorInvoke + + if ($ErrorInvoke) { + ## No existing manifest retrieved, submit as new manifest + Write-Verbose "No manifest that matched. Package Identifier: $($Manifest.PackageIdentifier)" + + $ApiMethod = $ApiMethodPost + $AzFunctionURL = $AzFunctionURLBase + $ApiHeader.Remove("x-functions-key") + $ApiHeader.Add("x-functions-key", $FunctionKeyPost) + } + else { + ## Existing manifest retrieved, submit as update existing manifest + Write-Verbose "Found manifest that matched. Package Identifier: $($Manifest.PackageIdentifier)" + + if($Response.Data.Count -ne 1) { + Write-Error "Found conflicting manifests. Package Identifier: $($Manifest.PackageIdentifier)" + return + } + + $ApiMethod = $ApiMethodPut + $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier + $ApiHeader.Remove("x-functions-key") + $ApiHeader.Add("x-functions-key", $FunctionKeyPut) + } + + Write-Verbose -Message "The Manifest will be added using the $ApiMethod REST API." + + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -Body $Manifest.GetJson() -ContentType $ApiContentType -ErrorVariable ErrorInvoke + + if($ErrorInvoke) { + $ErrReturnObject = @{ + AzFunctionURL = $AzFunctionURL + ApiMethod = $ApiMethod + ApiContentType = $ApiContentType + ApplicationManifest = $Manifest.GetJson() + Response = $Response + InvokeError = $ErrorInvoke } - ## Determines the REST API that will be called, generates keys, and performs either Add (Post) or Update (Put) action. - Write-Verbose -Message "The Manifest will be added using the $ApiMethod REST API." - $TriggerName = "Manifest$ApiMethod" - $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default - $ApiHeader.Add("x-functions-key", $FunctionKey) - - $Response += Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -Body $ApplicationManifest.GetJson() -ContentType $ApiContentType -ErrorVariable ErrorInvoke - - if($ErrorInvoke) { - $ErrReturnObject = @{ - AzFunctionURL = $AzFunctionURL - ApiHeader = $ApiHeader - ApiMethod = $ApiMethod - ApiContentType = $ApiContentType - ApplicationManifest = $Manifest.GetJson() - Response = $Response - InvokeError = $ErrorInvoke - } - - Write-Error -Message "Failed to add manifest." -TargetObject $ErrReturnObject + Write-Error -Message "Failed to add manifest." -TargetObject $ErrReturnObject + } + else { + if ($Response.Data.Count -ne 1) { + Write-Warning "Returned conflicting manifests after adding the manifest. Package Identifier: $($Manifest.PackageIdentifier)" + } + + foreach ($ResponseData in $Response.Data){ + Write-Verbose "Parsing through the returned results: $ResponseData" + $Return += [WinGetManifest]::CreateFromObject($ResponseData) } } } END { - ## If a package is created, return the objects data - if($Response.Data) { - return $Response.Data - } - ## If no new package is created, return a $null - else { - return $null - } + return $Return } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index b54bc04e..08fc9c82 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -41,78 +41,72 @@ Function Connect-ToAzure [Parameter(Position=0, Mandatory=$false)] [string]$SubscriptionName, [Parameter(Position=1, Mandatory=$false)] [string]$SubscriptionId ) - BEGIN - { - $TestAzureConnection = $false - - if($SubscriptionName -and $SubscriptionId){ - ## If connected to Azure, and the Subscription Name and Id are provided then verify that the connected Azure session matches the provided Subscription Name and Id. - Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" + + $TestAzureConnection = $false + + if($SubscriptionName -and $SubscriptionId){ + ## If connected to Azure, and the Subscription Name and Id are provided then verify that the connected Azure session matches the provided Subscription Name and Id. + Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId + } + elseif($SubscriptionName){ + ## If connected to Azure, and the Subscription Name are provided then verify that the connected Azure session matches the provided Subscription Name. + Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName + } + elseif($SubscriptionId -and $TestAzureConnection){ + ## If connected to Azure, and the Subscription Id are provided then verify that the connected Azure session matches the provided Subscription Id. + Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Id $SubscriptionId" + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId + } + else{ + Write-Information "No Subscription Name or Subscription Id provided. Will test connection to default Azure Subscription" + $TestAzureConnection = Test-ConnectionToAzure + } + + Write-Verbose -Message "Test Connection Result: $TestAzureConnection" + + if(!$TestAzureConnection) { + if($SubscriptionName -and $SubscriptionId) { + ## Attempts a connection to Azure using both the Subscription Name and Subscription Id + Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" + Connect-AzAccount -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId } - elseif($SubscriptionName){ - ## If connected to Azure, and the Subscription Name are provided then verify that the connected Azure session matches the provided Subscription Name. - Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName" + elseif($SubscriptionName) { + ## Attempts a connection to Azure using Subscription Name + Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName" + Connect-AzAccount -SubscriptionName $SubscriptionName + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName } - elseif($SubscriptionId -and $TestAzureConnection){ - ## If connected to Azure, and the Subscription Id are provided then verify that the connected Azure session matches the provided Subscription Id. - Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Id $SubscriptionId" + elseif($SubscriptionId) { + ## Attempts a connection to Azure using Subscription Id + Write-Information "Initiating a connection to your Azure Subscription Id $SubscriptionId" + Connect-AzAccount -SubscriptionId $SubscriptionId + $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId } else{ - Write-Information "No Subscription Name or Subscription Id provided. Will test connection to default Azure Subscription" + ## Attempts a connection to Azure with the users default Subscription + Write-Information "Initiating a connection to your Azure environment." + Connect-AzAccount + $TestAzureConnection = Test-ConnectionToAzure } - - Write-Verbose -Message "Test Connection Result: $TestAzureConnection" - } - PROCESS - { - if(!$TestAzureConnection) { - if($SubscriptionName -and $SubscriptionId) { - ## Attempts a connection to Azure using both the Subscription Name and Subscription Id - Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName and Subscription Id $SubscriptionId" - Connect-AzAccount -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId - - $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId - } - elseif($SubscriptionName) { - ## Attempts a connection to Azure using Subscription Name - Write-Information "Initiating a connection to your Azure Subscription Name $SubscriptionName" - Connect-AzAccount -SubscriptionName $SubscriptionName - - $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName - } - elseif($SubscriptionId) { - ## Attempts a connection to Azure using Subscription Id - Write-Information "Initiating a connection to your Azure Subscription Id $SubscriptionId" - Connect-AzAccount -SubscriptionId $SubscriptionId - - $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId - } - else{ - ## Attempts a connection to Azure with the users default Subscription - Write-Information "Initiating a connection to your Azure environment." - Connect-AzAccount - $TestAzureConnection = Test-ConnectionToAzure + if(!$TestAzureConnection) { + ## If the connection fails, or the user cancels the login request, then return as failed. + $ErrReturnObject = @{ + SubscriptionName = $SubscriptionName + SubscriptionId = $SubscriptionId + AzureConnected = $TestAzureConnection } - if(!$TestAzureConnection) { - ## If the connection fails, or the user cancels the login request, then throw an error. - $ErrReturnObject = @{ - SubscriptionName = $SubscriptionName - SubscriptionId = $SubscriptionId - AzureConnected = $TestAzureConnection - } - - Write-Error -Message "Failed to connect to Azure" -TargetObject $ErrReturnObject - } + Write-Error -Message "Failed to connect to Azure" -TargetObject $ErrReturnObject } } - END - { - return $TestAzureConnection - } + + return $TestAzureConnection } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 b/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 index 9ec7b4f7..114713fe 100644 --- a/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 +++ b/Tools/PowershellModule/src/Library/Get-PairedAzureRegion.ps1 @@ -6,256 +6,247 @@ Function Get-PairedAzureRegion PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$Region ) - BEGIN - { - $LocationList = Get-AzLocation - } - PROCESS - { - if($Region -like "*canada*") { - switch ($Region) { - "canadacentral" { - $Result = "canadaeast" - } - "canadaeast" { - $Result = "canadacentral" - } - Default { - $Result = "canadacentral" - } + + $Result = "westus" + + if($Region -like "*canada*") { + switch ($Region) { + "canadacentral" { + $Result = "canadaeast" } - } - elseif($Region -like "*asia*") { - switch ($Region) { - "eastasia" { - $Result = "southeastasia" - } - "southeastasia" { - $Result = "eastasia" - } - Default { - $Result = "eastasia" - } + "canadaeast" { + $Result = "canadacentral" } - } - elseif($Region -like "*japan*") { - switch ($Region) { - "japanwest" { - $Result = "japaneast" - } - "japaneast" { - $Result = "japanwest" - } - Default { - $Result = "japanwest" - } + Default { + $Result = "canadacentral" } } - elseif($Region -like "*europe*") { - switch ($Region) { - "northeurope" { - $Result = "westeurope" - } - "westeurope" { - $Result = "northeurope" - } - Default { - $Result = "westeurope" - } + } + elseif($Region -like "*asia*") { + switch ($Region) { + "eastasia" { + $Result = "southeastasia" + } + "southeastasia" { + $Result = "eastasia" + } + Default { + $Result = "eastasia" } } - elseif($Region -like "*brazil*") { - switch ($Region) { - "brazilsouth" { - $Result = "brazilsoutheast" - } - "brazilsoutheast" { - $Result = "brazilsouth" - } - Default { - $Result = "brazilsoutheast" - } + } + elseif($Region -like "*japan*") { + switch ($Region) { + "japanwest" { + $Result = "japaneast" + } + "japaneast" { + $Result = "japanwest" + } + Default { + $Result = "japanwest" } } - elseif($Region -like "*australia*") { - switch ($Region) { - "australiaeast" { - $Result = "australiasoutheast" - } - "australiasoutheast" { - $Result = "australiaeast" - } - "australiacentral" { - $Result = "australiacentral2" - } - "australiacentral2" { - $Result = "australiacentral" - } - Default { - $Result = "australiasoutheast" - } + } + elseif($Region -like "*europe*") { + switch ($Region) { + "northeurope" { + $Result = "westeurope" + } + "westeurope" { + $Result = "northeurope" + } + Default { + $Result = "westeurope" } } - elseif($Region -like "*us*") { - switch ($Region) { - "eastus" { - $Result = "westus" - } - "westus" { - $Result = "centralus" - } - "eastus2" { - $Result = "centralus" - } - "centralus" { - $Result = "eastus2" - } - "northcentralus" { - $Result = "southcentralus" - } - "southcentralus" { - $Result = "northcentralus" - } - "westus2" { - $Result = "westcentralus" - } - "westcentralus" { - $Result = "westus2" - } - "westus3" { - $Result = "westcentralus" - } - Default { - $Result = "westus" - } + } + elseif($Region -like "*brazil*") { + switch ($Region) { + "brazilsouth" { + $Result = "brazilsoutheast" + } + "brazilsoutheast" { + $Result = "brazilsouth" + } + Default { + $Result = "brazilsoutheast" } } - elseif($Region -like "*india*") { - switch ($Region) { - "westindia" { - $Result = "southindia" - } - "centralindia" { - $Result = "southindia" - } - "southindia" { - $Result = "centralindia" - } - Default { - $Result = "southindia" - } + } + elseif($Region -like "*australia*") { + switch ($Region) { + "australiaeast" { + $Result = "australiasoutheast" + } + "australiasoutheast" { + $Result = "australiaeast" + } + "australiacentral" { + $Result = "australiacentral2" + } + "australiacentral2" { + $Result = "australiacentral" + } + Default { + $Result = "australiasoutheast" } } - elseif($Region -like "*uk*") { - switch ($Region) { - "uksouth" { - $Result = "ukwest" - } - "ukwest" { - $Result = "uksouth" - } - Default { - $Result = "uksouth" - } + } + elseif($Region -like "*us*") { + switch ($Region) { + "eastus" { + $Result = "westus" + } + "westus" { + $Result = "centralus" + } + "eastus2" { + $Result = "centralus" + } + "centralus" { + $Result = "eastus2" + } + "northcentralus" { + $Result = "southcentralus" + } + "southcentralus" { + $Result = "northcentralus" + } + "westus2" { + $Result = "westcentralus" + } + "westcentralus" { + $Result = "westus2" + } + "westus3" { + $Result = "westcentralus" + } + Default { + $Result = "westus" } } - elseif($Region -like "*korea*") { - switch ($Region) { - "koreacentral" { - $Result = "koreasouth" - } - "koreasouth" { - $Result = "koreacentral" - } - Default { - $Result = "koreacentral" - } + } + elseif($Region -like "*india*") { + switch ($Region) { + "westindia" { + $Result = "southindia" + } + "centralindia" { + $Result = "southindia" + } + "southindia" { + $Result = "centralindia" + } + Default { + $Result = "southindia" } } - elseif($Region -like "*france*") { - switch ($Region) { - "francecentral" { - $Result = "francesouth" - } - "francesouth" { - $Result = "francecentral" - } - Default { - $Result = "francecentral" - } + } + elseif($Region -like "*uk*") { + switch ($Region) { + "uksouth" { + $Result = "ukwest" + } + "ukwest" { + $Result = "uksouth" + } + Default { + $Result = "uksouth" } } - elseif($Region -like "*africa*") { - switch ($Region) { - "southafricanorth" { - $Result = "southafricawest" - } - "southafricawest" { - $Result = "southafricanorth" - } - Default { - $Result = "southafricanorth" - } + } + elseif($Region -like "*korea*") { + switch ($Region) { + "koreacentral" { + $Result = "koreasouth" + } + "koreasouth" { + $Result = "koreacentral" + } + Default { + $Result = "koreacentral" } } - elseif($Region -like "*switzerland*") { - switch ($Region) { - "switzerlandnorth" { - $Result = "switzerlandwest" - } - "switzerlandwest" { - $Result = "switzerlandnorth" - } - Default { - $Result = "switzerlandwest" - } + } + elseif($Region -like "*france*") { + switch ($Region) { + "francecentral" { + $Result = "francesouth" + } + "francesouth" { + $Result = "francecentral" + } + Default { + $Result = "francecentral" } } - elseif($Region -like "*germany*") { - switch ($Region) { - "germanynorth" { - $Result = "germanywestcentral" - } - "germanywestcentral" { - $Result = "germanynorth" - } - Default { - $Result = "germanywestcentral" - } + } + elseif($Region -like "*africa*") { + switch ($Region) { + "southafricanorth" { + $Result = "southafricawest" + } + "southafricawest" { + $Result = "southafricanorth" + } + Default { + $Result = "southafricanorth" } } - elseif($Region -like "*norway*") { - switch ($Region) { - "norwaywest" { - $Result = "norwayeast" - } - "norwayeast" { - $Result = "norwaywest" - } - Default { - $Result = "norwaywest" - } + } + elseif($Region -like "*switzerland*") { + switch ($Region) { + "switzerlandnorth" { + $Result = "switzerlandwest" + } + "switzerlandwest" { + $Result = "switzerlandnorth" + } + Default { + $Result = "switzerlandwest" } } - elseif($Region -like "*uae*") { - switch ($Region) { - "uaecentral" { - $Result = "uaenorth" - } - "uaenorth" { - $Result = "uaecentral" - } - Default { - $Result = "uaecentral" - } + } + elseif($Region -like "*germany*") { + switch ($Region) { + "germanynorth" { + $Result = "germanywestcentral" + } + "germanywestcentral" { + $Result = "germanynorth" + } + Default { + $Result = "germanywestcentral" } } - else { - $Result = "westus" + } + elseif($Region -like "*norway*") { + switch ($Region) { + "norwaywest" { + $Result = "norwayeast" + } + "norwayeast" { + $Result = "norwaywest" + } + Default { + $Result = "norwaywest" + } } } - END - { - Return $Result + elseif($Region -like "*uae*") { + switch ($Region) { + "uaecentral" { + $Result = "uaenorth" + } + "uaenorth" { + $Result = "uaecentral" + } + Default { + $Result = "uaecentral" + } + } } + + return $Result } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 4c39f965..2710bb8d 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -22,14 +22,11 @@ Function Get-WinGetManifest A JSON string containing a single application's REST source Packages Manifest that will be merged with locally processed .yaml files. This is used by the script infrastructure internally and is not expected to be useful to an end user using this command. - .PARAMETER URL - Web URL to the host site containing the REST APIs with access key (if required). - .PARAMETER FunctionName Name of the Azure Function Name that contains the Windows Package Manager REST APIs. .PARAMETER PackageIdentifier - [Optional] The Windows Package Manager Package Identifier of a specific Package Manifest result. + [Optional] Supports input from pipeline. The Windows Package Manager Package Identifier of a specific Package Manifest result. .PARAMETER SubscriptionName [Optional] Name of the Azure Subscription that contains the Azure Function which contains the REST APIs. @@ -65,7 +62,7 @@ Function Get-WinGetManifest [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$JSON, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Azure")] [string]$FunctionName, - [Parameter(Position=1, Mandatory=$false,ParameterSetName="Azure")] [string]$PackageIdentifier, + [Parameter(Position=1, Mandatory=$false,ParameterSetName="Azure", ValueFromPipeline=$true)] [string]$PackageIdentifier, [Parameter(Position=2, Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName ) BEGIN @@ -79,45 +76,82 @@ Function Get-WinGetManifest ############################### ## Connects to Azure, if not already connected. Write-Verbose "Testing connection to Azure." - $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { - throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." -ErrorAction Stop } ############################### - ## Verify Azure Resources Exist - ## Sets variables as if the Azure Function Name was provided. - - $AzureResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName - - if($AzureResourceGroupName) { - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $AzureResourceGroupName - - if(!$Result) { - throw "Failed to confirm resources exist in Azure. Please verify and try again." - } + ## Gets Resource Group name of the Azure Function + Write-Verbose -Message "Determines the Azure Function Resource Group Name" + $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName + if(!$ResourceGroupName) { + Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - else { - throw "Unable to locate Function (""$FunctionName"") with Resource Group in the Azure Tenant." + + ############################### + ## Verify Azure Resources Exist + Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName + if(!$Result) { + Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop } - if($PackageIdentifier){ - $PackageIdentifier = "/$PackageIdentifier" - } - - ############################### - ## REST api call + ## Retrieves the Azure Function URL used to add new manifests to the REST source + Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." + $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + + $FunctionAppId = $FunctionApp.Id + $DefaultHostName = $FunctionApp.DefaultHostName - ## Specifies the REST api call that will be performed $TriggerName = "ManifestGet" $ApiContentType = "application/json" $ApiMethod = "Get" - + ## Creates the API Post Header $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $ApiHeader.Add("Accept", 'application/json') - } + $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default + $ApiHeader.Add("x-functions-key", $FunctionKey) + } + "File" { + ## Nothing to prepare + } + } + } + PROCESS + { + switch ($PsCmdlet.ParameterSetName) { + "Azure" { + $AzFunctionURL = "https://" + $DefaultHostName + "/api/packageManifests/" + $PackageIdentifier + + ## Publishes the Manifest to the Windows Package Manager REST source + Write-Verbose -Message "Invoking the REST API call." + + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType -ErrorVariable ErrorInvoke + + if($ErrorInvoke) { + $ErrorMessage = "Failed to get Manifest from $FunctionName. Verify the information you provided and try again." + $ErrReturnObject = @{ + AzFunctionURL = $AzFunctionURL + ApiMethod = $ApiMethod + ApiContentType = $ApiContentType + Response = $Response + InvokeError = $ErrorInvoke + } + + Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + } + else { + Write-Verbose "Found ($($Response.Data.Count)) Manifests that matched." + + foreach ($ResponseData in $Response.Data){ + Write-Verbose -Message "Parsing through the returned results: $ResponseData" + $Return += [WinGetManifest]::CreateFromObject($ResponseData) + Write-Information "Returned Manifest from JSON file: $($Return[-1].PackageIdentifier)" + } + } + } "File" { ## Convert to full path if applicable $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) @@ -131,7 +165,7 @@ Function Get-WinGetManifest } Write-Error -Message "Target path did not point to an object." -TargetObject $ErrReturnObject - return $Return + return } $PathItem = Get-Item $Path @@ -144,47 +178,46 @@ Function Get-WinGetManifest $PathChildItemsJSON = Get-ChildItem -Path $Path -Filter "*.json" $PathChildItemsYAML = Get-ChildItem -Path $Path -Filter "*.yaml" - Write-Verbose -Message "Path pointed to a directory, found $($PathChildItemsJSON.count) JSON files, and $($PathChildItemsYAML.count) YAML files." + Write-Verbose -Message "Path pointed to a directory, found $($PathChildItemsJSON.count) JSON files, and $($PathChildItemsYAML.Count) YAML files." $ErrReturnObject = @{ JSONFiles = $PathChildItemsJSON YAMLFiles = $PathChildItemsYAML - JSONCount = $PathChildItemsJSON.count - YAMLCount = $PathChildItemsYAML.count + JSONCount = $PathChildItemsJSON.Count + YAMLCount = $PathChildItemsYAML.Count } ## Validating found objects - if($PathChildItemsJSON.count -eq 0 -and $PathChildItemsYAML.count -eq 0) { + if ($PathChildItemsJSON.Count -eq 0 -and $PathChildItemsYAML.Count -eq 0) { ## No JSON or YAML files were found in the directory. $ErrorMessage = "Directory does not contain any JSON or YAML files." Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject - return $Return + return } - elseif($PathChildItemsJSON.count -gt 0 -and $PathChildItemsYAML.count -gt 0) { + elseif ($PathChildItemsJSON.Count -gt 0 -and $PathChildItemsYAML.Count -gt 0) { ## A combination of JSON and YAML Files were found. $ErrorMessage = "Directory contains a combination of JSON and YAML files." Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject - return $Return + return } - elseif($PathChildItemsJSON.count -gt 1) { + elseif ($PathChildItemsJSON.Count -gt 1) { ## More than one Package Manifest's JSON files was found. $ErrorMessage = "Directory contains more than one JSON file." Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject - return $Return + return } - elseif($PathChildItemsJSON.count -eq 1) { + elseif ($PathChildItemsJSON.Count -eq 1) { ## Single JSON has been found in the target folder. Write-Verbose -Message "Single JSON has been found in the specified directory." $ManifestFile = $PathChildItemsJSON - $ApplicationManifest = Get-Content -Path $PathChildItemsJSON.FullName -Raw + $ApplicationManifest = Get-Content -Path $PathChildItemsJSON[0].FullName -Raw $ManifestFileType = ".json" } - elseif($PathChildItemsYAML.count -gt 0) { + elseif ($PathChildItemsYAML.Count -gt 0) { Write-Verbose -Message "YAML has been found in the specified directory." ## YAML has been found in the target folder. $ManifestFile = $PathChildItemsYAML $ManifestFileType = ".yaml" - $ApplicationManifest = Get-Content -Path $PathChildItemsYAML[0].FullName -Raw } } else { @@ -194,54 +227,17 @@ Function Get-WinGetManifest ## Gets the Manifest object and contents of the Manifest - identifying the manifest file extension. $ApplicationManifest = Get-Content -Path $Path -Raw $ManifestFile = Get-Item -Path $Path - $ManifestFileType = $ManifestFile.Extension + $ManifestFileType = $ManifestFile.Extension.ToLower() Write-Verbose -Message "Retrieved content from the manifest ($($ManifestFile.Name))." } - } - } - } - PROCESS - { - switch ($PsCmdlet.ParameterSetName) { - "Azure" { - Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." - - ## Retrieves the Azure Function URL used to add new manifests to the REST source - $FunctionApp = Get-AzWebApp -ResourceGroupName $AzureResourceGroupName -Name $FunctionName - - ## can function key be part of the header - Write-Verbose -Message "Constructing the REST API call." - $FunctionAppId = $FunctionApp.Id - $DefaultHostName = $FunctionApp.DefaultHostName - $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default - $ApiHeader.Add("x-functions-key", $FunctionKey) - $AzFunctionURL = "https://" + $DefaultHostName + "/api/packageManifests" + $PackageIdentifier - - ## Publishes the Manifest to the Windows Package Manager REST source - Write-Verbose -Message "Invoking the REST API call." - - $Results = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType - Write-Verbose "Found ($($Results.Data.Count)) Manifests that matched." - - foreach ($Result in $Results.Data){ - Write-Verbose -Message "Parsing through the returned results: $Result" - $Return += [WinGetManifest]::CreateFromObject($Result) - } - } - "File" { switch ($ManifestFileType) { ## If the path resolves to a JSON file ".json" { - $Result = Test-WinGetManifest -Manifest $ApplicationManifest - - if($Result) { - ## Sets the return result to be the contents of the JSON file if the Manifest test passed. - $Return += [WinGetManifest]::CreateFromString($ApplicationManifest) - - Write-Information "Returned Manifest from JSON file: $($Return.PackageIdentifier)" - } + ## Sets the return result to be the contents of the JSON file if the Manifest test passed. + $Return += [WinGetManifest]::CreateFromString($ApplicationManifest) + Write-Information "Returned Manifest from JSON file: $($Return[-1].PackageIdentifier)" } ## If the path resolves to a YAML file ".yaml" { @@ -256,7 +252,7 @@ Function Get-WinGetManifest $Return += [WinGetManifest]::CreateFromString([Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, "")) } - Write-Information "Returned Manifest from YAML file: $($Return.PackageIdentifier)" + Write-Information "Returned Manifest from JSON file: $($Return[-1].PackageIdentifier)" } default { $ErrorMessage = "Incorrect file type. Verify the file is of type '*.yaml' or '*.json' and try again." @@ -266,7 +262,7 @@ Function Get-WinGetManifest $ManifestFileType = $ManifestFileType } - Write-Error -Message $ErrorMessage -Category InvalidType -TargetObject $ErrReturnObject + Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject } } } @@ -275,7 +271,7 @@ Function Get-WinGetManifest END { ## Returns results - Write-Verbose -Message "Returning ($($Return.count)) manifests based on search." + Write-Verbose -Message "Returning ($($Return.Count)) manifests based on search." return $Return } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 196b0252..8704c25c 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -32,10 +32,10 @@ Function New-ARMObjects BEGIN { ## Imports the contents of the Parameter Files for reference and logging purposes: - $jsonStorageAccount = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "StorageAccount" }).ParameterPath) -ErrorAction SilentlyContinue | ConvertFrom-Json - $jsoncdba = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "CosmosDBAccount"}).ParameterPath) -ErrorAction SilentlyContinue | ConvertFrom-Json - $jsonKeyVault = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Keyvault"}).ParameterPath) -ErrorAction SilentlyContinue | ConvertFrom-Json - $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) -ErrorAction SilentlyContinue | ConvertFrom-Json + $jsonStorageAccount = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "StorageAccount" }).ParameterPath) | ConvertFrom-Json + $jsoncdba = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "CosmosDBAccount"}).ParameterPath) | ConvertFrom-Json + $jsonKeyVault = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Keyvault"}).ParameterPath) | ConvertFrom-Json + $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) | ConvertFrom-Json ## Azure resource names retrieved from the Parameter files. $AzKeyVaultName = $jsonKeyVault.parameters.name.value @@ -54,30 +54,18 @@ Function New-ARMObjects PROCESS { ## Creates the Azure Resources following the ARM template / parameters - Write-Verbose -Message "Creating Azure Resources following ARM Templates." - Write-Information -MessageData "Creating Azure Resources following ARM Templates." + Write-Information "Creating Azure Resources following ARM Templates." ## This is order specific, please ensure you used the New-ARMParameterObject function to create this object in the pre-determined order. foreach ($Object in $ARMObjects) { - Write-Verbose -Message " Creating the Azure Object - $($Object.ObjectType)" - Write-Information -MessageData " Creating the Azure Object - $($Object.ObjectType)" + Write-Information " Creating the Azure Object - $($Object.ObjectType)" ## If the object to be created is an Azure Function, then complete these pre-required steps before creating the Azure Function. if($Object.ObjectType -eq "Function") { - Write-Verbose -Message " Creating KeyVault Secrets:" - Write-Information -MessageData " Creating KeyVault Secrets:" + Write-Verbose " Creating KeyVault Secrets:" ## Creates a reference to the Azure Storage Account Connection String as a Secret in the Azure Keyvault. - $AzStorageAccountKey = $(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -Name $AzStorageAccountName)[0].Value - - ## Retrieves the required information from the previously created Azure objects. Values will be used to generate required information for the Azure Keyvault. - ## [TODO:] Fix the secure string readings that were removed to unblock the 1ES pipeline migration. - ## The previous usage of the secure string readings was causing failures in the static analysis job of the pipeline. - ## https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/rules/avoidusingconverttosecurestringwithplaintext?view=ps-modules - $CosmosAccountEndpointValue = "" - $CosmosAccountKeyWriteValue = "" - $CosmosAccountKeyReadValue = "" - $AzStorageAccountConnectionString = "" + $AzStorageAccountKey = $(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -Name $AzStorageAccountName)[0].Valu ## Adds the Azure Storage Account Connection String to the Keyvault Write-Verbose -Message " Creating Keyvault Secret for Azure Storage Account Connection String." @@ -102,13 +90,13 @@ Function New-ARMObjects ## Create base object of the Azure Function, generating reference object ID for Keyvault Write-Verbose -Message " Creating base Azure Function object." Write-Information -MessageData " Creating base Azure Function object." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue + $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental } ## Creates the Azure Resource Write-Verbose -Message " Creating $($Object.ObjectType) following the ARM Parameter File..." Write-Information -MessageData " Creating $($Object.ObjectType) following the ARM Parameter File..." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorAction SilentlyContinue -ErrorVariable objerror -AsJob + $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable objerror -AsJob while ($Result.State -eq "Running") { ## Sets a sleep of 10 seconds after object creation to allow Azure to update creation status, and mark as "running" @@ -127,7 +115,7 @@ Function New-ARMObjects ## Creating the object following the ARM template failed. ## TODO: extend error reporting in logs across scripts. Write-Error "Failed to create Azure object. $($Result.Error)" -TargetObject $ErrReturnObject - Return + return } else { ## Creating the object was successful @@ -166,6 +154,6 @@ Function New-ARMObjects } END { - Return + return } } diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 index 81793f42..809aaaf1 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 @@ -34,7 +34,7 @@ Function New-ARMParameterObject #> PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$ParameterFolderPath, - [Parameter(Position=1, Mandatory=$false)][string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplate", + [Parameter(Position=1, Mandatory=$true)] [string]$TemplateFolderPath, [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance @@ -496,6 +496,6 @@ Function New-ARMParameterObject END { ## Returns the completed object. - Return $ARMObjects + return $ARMObjects } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index 4296c640..cfbf2f92 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -17,16 +17,19 @@ Function New-WinGetSource resources will be created in in this Resource Group (Default: WinGetRestsource) .PARAMETER SubscriptionName - [Optional] The name of the subscription that will be used to host the Windows Package Manager REST source. + [Optional] The name of the subscription that will be used to host the Windows Package Manager REST source. (Default: Current connected subscription) .PARAMETER Region [Optional] The Azure location where objects will be created in. (Default: westus) - .PARAMETER ParameterOutput - [Optional] The directory where Parameter objects will be created in. (Default: Current Directory) + .PARAMETER TemplateFolderPath + [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplate) + + .PARAMETER ParameterOutputPath + [Optional] The directory where Parameter objects will be created in. (Default: Current Directory\Parameters) .PARAMETER RestSourcePath - [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\Library\RestAPI\WinGet.RestSource.Functions.zip) + [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip) .PARAMETER ImplementationPerformance [Optional] ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. @@ -58,57 +61,45 @@ Function New-WinGetSource PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$Name, [Parameter(Position=1, Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", - [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName, + [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "", [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", - [Parameter(Position=4, Mandatory=$false)] [string]$ParameterOutput = $(Get-Location).Path, - [Parameter(Position=5, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\RestAPI\WinGet.RestSource.Functions.zip", + [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplate", + [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", + [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", [ValidateSet("Developer", "Basic", "Enhanced")] - [Parameter(Position=6, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [Parameter(Position=7, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", [Parameter()] [switch]$ShowConnectionInstructions ) BEGIN { if($ImplementationPerformance -eq "Developer") { - Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this.`n" + Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" } - - ## Paths to the Parameter and Template folders and the location of the Function Zip - $ParameterFolderPath = "$ParameterOutput\Parameters" - $TemplateFolderPath = "$PSScriptRoot\ARMTemplate" - - ## Outlines the Azure Modules that are required for this Function to work. - $RequiredModules = @("Az.Resources", "Az.Accounts", "Az.KeyVault","Az.Websites", "Az.Functions") } PROCESS { ############################### - ## Validates that the Azure Modules are installed - Write-Verbose -Message "Testing required PowerShell modules are installed." - - $RequiredModules = @("Az.Resources", "Az.Accounts", "Az.Websites", "Az.Functions", "Az.Storage") - $Result = Test-PowerShellModuleExist -Modules $RequiredModules - - if(!$Result) { - throw "Unable to run script, missing required PowerShell modules" + ## Check input paths + if(!$(Test-Path -Path $TemplateFolderPath)) { + throw "REST Source Function Code is missing in specified path ($TemplateFolderPath)" } - if(!$(Test-Path -Path $RestSourcePath)) - { + if(!$(Test-Path -Path $RestSourcePath)) { throw "REST Source Function Code is missing in specified path ($RestSourcePath)" } - ############################### - ## Create Folders for the Parameter folder paths - $ResultParameter = New-Item -ItemType Directory -Path $ParameterFolderPath -ErrorAction SilentlyContinue -InformationAction SilentlyContinue - - if($ResultParameter) { - Write-Verbose -Message "Created Directory to contain the ARM Parameter files ($($ResultParameter.FullName))." + ## Create folder for the Parameter output path + $Result = New-Item -ItemType Directory -Path $ParameterOutputPath -Force + if($Result) { + Write-Verbose -Message "Created Directory to contain the ARM Parameter files ($($Result.FullName))." + } + else { + throw "Failed to create ARM parameters output path. Path: $ParameterOutputPath" } ############################### ## Connects to Azure, if not already connected. - Write-Verbose -Message "Testing connection to Azure." - + Write-Information "Testing connection to Azure." $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." @@ -116,17 +107,16 @@ Function New-WinGetSource ############################### ## Creates the ARM files - $ARMObjects = New-ARMParameterObject -ParameterFolderPath $ParameterFolderPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance + $ARMObjects = New-ARMParameterObject -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance ############################### ## Create Resource Group - Write-Verbose -Message "Creating the Resource Group used to host the Windows Package Manager REST source." + Write-Information "Creating the Resource Group used to host the Windows Package Manager REST source. Name: $ResourceGroup, Region: $Region" Add-AzureResourceGroup -Name $ResourceGroup -Region $Region - #### Verifies ARM Parameters are correct #### - $Result = Test-ARMTemplate -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup -ErrorAction SilentlyContinue -ErrorVariable err - - if($err){ + #### Verifies ARM Parameters are correct + $Result = Test-ARMTemplate -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup + if($Result){ $ErrReturnObject = @{ ARMObjects = $ARMObjects ResourceGroup = $ResourceGroup @@ -136,17 +126,6 @@ Function New-WinGetSource Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject } - - ## If the attempt fails.. then exit. - if($($Result)) { - $ErrReturnObject = @{ - Result = $Result - } - - Write-Error -Message "ARM Template and Parameter testing failed`n" -TargetObject $ErrReturnObject - Return - } - ############################### ## Creates Azure Objects with ARM Templates and Parameters New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup @@ -171,6 +150,6 @@ Function New-WinGetSource } END { - Return $ARMObjects + return $true } } diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index 873ff65d..dc04e68b 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -14,7 +14,11 @@ Function Remove-WinGetManifest Name of the Azure Function that hosts the REST source. .PARAMETER PackageIdentifier - The Package Id that represents the App Manifest to be removed. + Supports input from pipeline by property. The Package Id that represents the App Manifest to be removed. + + .PARAMETER PackageVersion + [Optional] Supports input from pipeline by property. The Package version that represents the App Manifest to be removed. + If empty, the whole package (all versions) will be removed. .PARAMETER SubscriptionName [Optional] The Subscription name that contains the Windows Package Manager REST source @@ -28,45 +32,44 @@ Function Remove-WinGetManifest #> PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, - [Parameter(Position=2, Mandatory=$true)] [string]$PackageIdentifier, - [Parameter(Position=2, Mandatory=$false)] [string]$PackageVersion = "", + [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string]$PackageIdentifier, + [Parameter(Position=2, Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [string]$PackageVersion = "", [Parameter(Position=3, Mandatory=$false)] [string]$SubscriptionName = "" ) BEGIN { + [PSCustomObject[]]$Return = @() + ############################### ## Connects to Azure, if not already connected. - Write-Verbose -Message "Testing connection to Azure." + Write-Verbose -Message "Validating connection to azure, will attempt to connect if not already connected." $Result = Connect-ToAzure -SubscriptionName $SubscriptionName if(!($Result)) { - throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." -ErrorAction Stop } - - ## Sets variables as if the Azure Function Name was provided. + + ############################### + ## Gets Resource Group name of the Azure Function + Write-Verbose -Message "Determines the Azure Function Resource Group Name" $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName + if(!$ResourceGroupName) { + Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop + } ############################### ## Verify Azure Resources Exist + Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName if(!$Result) { - throw "Failed to confirm resources exist in Azure. Please verify and try again." - } - - if($PackageIdentifier){ - $PackageIdentifier = "$PackageIdentifier" + Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop } ############################### ## REST api call ## Specifies the REST api call that will be performed - $TriggerName = "" - if([string]::IsNullOrWhiteSpace($PackageVersion)) { - $TriggerName = "ManifestDelete" - } - else { - $TriggerName = "VersionDelete" - } + $TriggerNameManifestDelete = "ManifestDelete" + $TriggerNameVersionDelete = "VersionDelete" $ApiContentType = "application/json" $ApiMethod = "Delete" @@ -76,45 +79,55 @@ Function Remove-WinGetManifest ## can function key be part of the header $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName - $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default + $FunctionKeyManifestDelete = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerNameManifestDelete" -Action listkeys -Force).default + $FunctionKeyVersionDelete = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerNameVersionDelete" -Action listkeys -Force).default ## Creates the API Post Header $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" $ApiHeader.Add("Accept", 'application/json') - $ApiHeader.Add("x-functions-key", $FunctionKey) - $AzFunctionURL = "" - if([string]::IsNullOrWhiteSpace($PackageVersion)) { - $AzFunctionURL = "https://" + $DefaultHostName + "/api/packageManifests/" + $PackageIdentifier - } - else { - $AzFunctionURL = "https://" + $DefaultHostName + "/api/packages/" + $PackageIdentifier + "/versions/" + $PackageVersion - } + $AzFunctionURLBase = "https://" + $DefaultHostName + "/api/" } PROCESS { - Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." Write-Verbose -Message "Constructing the REST API call for removal of manifest." + $AzFunctionURL = $AzFunctionURLBase + if([string]::IsNullOrWhiteSpace($PackageVersion)) { + $AzFunctionURL += "packageManifests/" + $PackageIdentifier + $ApiHeader.Remove("x-functions-key") + $ApiHeader.Add("x-functions-key", $FunctionKeyManifestDelete) + } + else { + $AzFunctionURL += "packages/" + $PackageIdentifier + "/versions/" + $PackageVersion + $ApiHeader.Remove("x-functions-key") + $ApiHeader.Add("x-functions-key", $FunctionKeyVersionDelete) + } + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType -ErrorVariable ErrorInvoke if($ErrorInvoke) { $ErrorMessage = "Failed to remove Manifest from $FunctionName. Verify the information you provided and try again." $ErrReturnObject = @{ AzFunctionURL = $AzFunctionURL - ApiHeader = $ApiHeader ApiMethod = $ApiMethod ApiContentType = $ApiContentType Response = $Response InvokeError = $ErrorInvoke } - ## If the Post failed, then return User specific error messages: - Write-Error -Message $ErrorMessage -Category ResourceUnavailable -TargetObject $ErrReturnObject + Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + } + else + { + $Return += [PSCustomObject]@{ + PackageIdentidier = $PackageIdentifier + PackageVersion = $PackageVersion + } } } END { - return $Response.Data + return $Return } } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 b/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 deleted file mode 100644 index 41fe7b3f..00000000 --- a/Tools/PowershellModule/src/Library/Test-ARMResourceName.ps1 +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Function Test-ARMResourceName -{ - <# - .SYNOPSIS - Validates that the name that will be assigned to the Azure Resource will meet the resource types requirement. - - .DESCRIPTION - Validates that the name that will be assigned to the Azure Resource will meet the resource types requirement. - - .PARAMETER ResourceType - The type of Azure Resource to validate requirements against. - - .PARAMETER ResourceName - The name that the resource type will be validated against. - - .PARAMETER ARMObject - The ARMObject provided by New-ARMParameterObjects. - - .EXAMPLE - Test-ARMResourceName -ARMObject $ARMObject - - Parses through the $ARMObject array, recalling this function for each object in the array validating that the - name meets the Azure resource requirements. - - .EXAMPLE - Test-ARMResourceName -ResourceType "AppInsight" -ResourceName "contosorestsource" - - Verifies that the name "contosorestsource" meets the requirements for Azure App Insights. - #> - PARAM( - [Parameter(Position=0, Mandatory=$true, ParameterSetName="Targetted")] - [ValidateSet("AppInsight", "KeyVault", "StorageAccount", "asp", "CosmosDBAccount", "CosmosDBDatabase", "CosmosDBContainer", "Function", "FrontDoor", "AppConfig")][String] $ResourceType, - [Parameter(Position=1, Mandatory=$true, ParameterSetName="Targetted")][String] $ResourceName, - [Parameter(Position=0, Mandatory=$true, ParameterSetName="SingleObject")] $ARMObject - ) - BEGIN - { - ## Allows for a single instance of the ARM Object to be passed in - if($PSCmdlet.ParameterSetName -eq "SingleObject") { - ## Sets the required variables based on the ARM Object - $ResourceType = $ARMObject.ObjectType - $ResourceName = $ARMObject.ObjectName - } - - ## Preset output experience - $TextPaddingRight = 30 - $TextPaddingRightChar = " " - - ## Creates an array of values to be compared against - $LowerAlphabet = @("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z") - $UpperAlphabet = @("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z") - $Numbers = @("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") - $SpecialChar = @("!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "{", "}", "[", "]", ":", ";", """", "'", "<", ",", ">", ".", "?", "/", "|", "\") - - ## Pre-Sets Values to False until proven otherwise - $NameContainsHyphen = $false - $NameContainsUnderscore = $false - $NameContainsConsecutiveHyphen = $false - $NameContainsConsecutiveunderscore = $false - $NameStartsWithLetter = $false - $NameStartsWithNumber = $false - $NameEndsWithLetter = $false - $NameEndsWithNumber = $false - $NameContainsLowerCase = $false - $NameContainsUpperCase = $false - $NameContainsNumber = $false - $NameContainsSpecialChar = $false - $NameLengthInRange = $false - $NameContainsSpaces = $false - - ## Sets the Values accordingly - $Result = $False - $NameLength = $ResourceName.Length - $NameContainsHyphen = $ResourceName.Contains("-") - $NameContainsUnderscore = $ResourceName.Contains("_") - $NameContainsSpaces = $ResourceName.Contains(" ") - $NameContainsConsecutiveHyphen = $ResourceName.Contains("--") - $NameContainsConsecutiveunderscore = $ResourceName.Contains("__") - - ## Validates if the name starts with, ends with or contains a number - foreach($Number in $Numbers){if($ResourceName.StartsWith($Number)){$NameStartsWithNumber = $True}} - foreach($Number in $Numbers){if($ResourceName.EndsWith($Number)) {$NameEndsWithNumber = $True}} - foreach($Number in $Numbers){if($ResourceName.Contains($Number)) {$NameContainsNumber = $True}} - - ## Validates if the name starts with or ends with a letter - foreach($Letter in $LowerAlphabet){if($ResourceName.ToLower().StartsWith($Letter)){$NameStartsWithLetter = $True}} - foreach($Letter in $LowerAlphabet){if($ResourceName.ToLower().EndsWith($Letter)) {$NameEndsWithLetter = $True}} - - ## Validates that the name contains an upper, lower or special characters - foreach($Letter in $LowerAlphabet){if($ResourceName.Contains($Letter)){$NameContainsLowerCase = $True}} - foreach($Letter in $LowerAlphabet){if($ResourceName.Contains($Letter)){$NameContainsLowerCase = $True}} - foreach($Letter in $UpperAlphabet){if($ResourceName.Contains($Letter)){$NameContainsUpperCase = $True}} - foreach($Char in $SpecialChar) {if($ResourceName.Contains($Char)) {$NameContainsSpecialChar = $True}} - - } - PROCESS - { - switch ($ResourceType) { - "KeyVault" { - ## Alphanumerics, and Hyphens, starts with letter, ends with letter or number. Con't contain consecutive hyphens. Length: 3-24 - if($($NameLength -ge 3) -and $($NameLength -le 24)) { - $NameLengthInRange = $true - } - - if($NameLengthInRange -and !$NameContainsSpecialChar -and !$NameStartsWithNumber -and !$NameContainsConsecutiveHyphen) { - $Result = $true - } - - ## Outputs the tests to the screen and their status - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - if($NameLengthInRange) { Write-Verbose -Message $(" Name within Length: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) } else { Write-Warning -Message $(" Name within Length: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) } - if(!$NameContainsSpecialChar) { Write-Verbose -Message $(" No Special Chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) } else { Write-Warning -Message $(" No Special Chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) } - if(!$NameStartsWithNumber) { Write-Verbose -Message $(" Doesn't start with Num:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameStartsWithNumber) } else { Write-Warning -Message $(" Doesn't start with Num:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameStartsWithNumber) } - if(!$NameContainsConsecutiveHyphen) { Write-Verbose -Message $(" No consecutive hyphens:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsConsecutiveHyphen)} else { Write-Warning -Message $(" No consecutive hyphens:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsConsecutiveHyphen)} - Write-Information -MessageData $(" -".PadRight($TextPaddingRight+6, "-")) - Write-Information -MessageData $(" Validation Result:".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - } - "StorageAccount" { - ## Alphanumerics, Hyphens, and underscores, Length: 3-60 - if($($NameLength -ge 3) -and $($NameLength -le 60)) { - $NameLengthInRange = $true - } - - if($NameLengthInRange -and !$NameContainsSpecialChar -and !$NameContainsUpperCase) { - $Result = $true - } - - ## Outputs the tests to the screen and their status - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - if($NameLengthInRange) { Write-Verbose -Message $(" Name within length: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) } else { Write-Warning -Message $(" Name within length: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) } - if(!$NameContainsSpecialChar) { Write-Verbose -Message $(" No special chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) } else { Write-Warning -Message $(" No special chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) } - if(!$NameContainsUpperCase) { Write-Verbose -Message $(" No upper case chars:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsUpperCase) } else { Write-Warning -Message $(" No upper case chars:".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsUpperCase) } - if(!$NameContainsSpaces) { Write-Verbose -Message $(" No spaces: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpaces) } else { Write-Warning -Message $(" No spaces: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpaces) } - Write-Information -MessageData $(" -".PadRight($TextPaddingRight+6, "-")) - Write-Information -MessageData $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - } - "asp" { - $NameLengthInRange = $true - - if($NameLengthInRange -and !$NameContainsSpaces) { - $Result = $true - } - - ## Outputs the tests to the screen and their status - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - if(!$NameContainsSpaces) { Write-Verbose -Message $(" No spaces: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpaces) } else { Write-Warning -Message $(" No spaces: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpaces) } - Write-Information -MessageData $(" -".PadRight($TextPaddingRight+6, "-")) - Write-Information -MessageData $(" Validation Result:".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - } - "Function" { - ## Alphanumerics, Hyphens, and underscores, Length: 3-60 - if($($NameLength -ge 3) -and $($NameLength -le 63)) { - $NameLengthInRange = $true - } - - if($NameLengthInRange -and !$NameContainsSpecialChar) { - $Result = $true - } - - ## Outputs the tests to the screen and their status - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Information -MessageData $("-".PadRight($TextPaddingRight+6, "-")) - Write-Information -MessageData $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - Write-Verbose -Message $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Verbose -Message $(" Name within Length:".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) - Write-Verbose -Message $(" No Special Chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) - } - "FrontDoor" { - ## Alphanumerics and hyphens. Start and end with alphanumeric. Length: 5-64 - if($($NameLength -ge 5) -and $($NameLength -le 64)) { - $NameLengthInRange = $true - } - - if($NameLengthInRange -and !$NameContainsSpecialChar) { - $Result = $true - } - - ## Outputs the tests to the screen and their status - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Information -MessageData $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - Write-Verbose -Message $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Verbose -Message $(" Name within Length:".PadRight($TextPaddingRight, $TextPaddingRightChar) + $NameLengthInRange) - Write-Verbose -Message $(" No Special Chars: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + !$NameContainsSpecialChar) - Write-Verbose -Message $("-".PadRight($TextPaddingRight+6, "-")) - Write-Verbose -Message $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Verbose -Message $("") - } - Default { - $Result = $true - - Write-Information -MessageData $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Information -MessageData $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Information -MessageData $("") - Write-Verbose -Message $(" Testing the ""$ResourceName"" name meets the requirements:") - Write-Verbose -Message $(" Validation Result: ".PadRight($TextPaddingRight, $TextPaddingRightChar) + $Result) - Write-Verbose -Message $("") - } - } - } - END - { - Return $Result - } -} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 index f373751b..97e649b8 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 @@ -9,12 +9,11 @@ Function Test-ARMTemplate .DESCRIPTION Validates that the parameter files have been build correctly, matches to the template files, and can be used to build Azure - resources. Will also validate that the naming used for the resources is available, and meets requirements. Returns boolean. - - True, if the validation testing passes - - False, if the validation testing fails. + resources. Will also validate that the naming used for the resources is available, and meets requirements. Returns a list of + failed validations. If all validations pass, returns empty. .PARAMETER ARMObjects - Object Returned from the Get-ARMParameterObject. + Object Returned from the New-ARMParameterObject. .PARAMETER ResourceGroup The Resource Group that the objects will be tested in reference to. @@ -29,55 +28,30 @@ Function Test-ARMTemplate [Parameter(Position=0, Mandatory=$true)] [array] $ARMObjects, [Parameter(Position=1, Mandatory=$true)] [string] $ResourceGroup ) - BEGIN - { - Write-Verbose "Verifying the ARM Resource Templates and Parameters are valid:" - $Return = @() - } - PROCESS - { - ## Parses through all ARM Parameter objects to validate they are properly configured. - foreach($Object in $ARMObjects) - { - ## Validates that each ARM object will work. - Write-Information -MessageData " Validation testing on ARM Resource ($($Object.ObjectType))." - $AzResourceResult = Test-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Mode Complete -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath - $AzNameResult = Test-ARMResourceName -ARMObject $Object - - ## If the ARM object fails validation, report error to screen. - if($AzResourceResult -ne "" -or !$AzNameResult) { - $ErrReturnObject = @{ - Test = $AzResourceResult - ARMObject = $Object - } - if($AzResourceResult -ne "") { - if($AzNameResult) { - $ErrorMessage = "$($Object.ObjectType) name is already in use, or there is an error with the Parameter file" - Write-Verbose "Name is already in use." - Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject - } - else { - Write-Error -Message "$($Object.ObjectType) name does not meet the requirements" -TargetObject $ErrReturnObject - } - } - ElseIF(!$AzNameResult) - { - Write-Error "$($Object.ObjectType) name does not meet the requirements." -TargetObject $ErrReturnObject - } + Write-Information "Verifying the ARM Resource Templates and Parameters are valid:" + [PSCustomObject[]]$Return = @() - $TestResult = @{ - ObjectType = $Object.ObjectType - ObjectName = $Object.Parameters.Parameters.Name - Result = $Result - } - $Return += $TestResult + ## Parses through all ARM Parameter objects to validate they are properly configured. + foreach($Object in $ARMObjects) + { + ## Validates that each ARM object will work. + Write-Information " Validation testing on ARM Resource ($($Object.ObjectType))." + $AzResourceResult = Test-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Mode Complete -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath + + ## If the ARM object fails validation, report error to screen. + if($AzResourceResult) { + [PSCustomObject]$ErrReturnObject = [PSCustomObject]@{ + ARMObject = $Object + TestResult = $AzResourceResult } + + Write-Error -Message "Failed to validate ARM Template with Template parameters. Template: $Object.TemplatePath, Parameters: $Object.ParameterPath" -TargetObject $ErrReturnObject + + $Return += $ErrReturnObject } } - End - { - ## Returns the TestResults. - Return $Return - } + + ## Returns the TestResults. + return $Return } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 b/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 index 861b283a..80d11553 100644 --- a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 +++ b/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 @@ -13,7 +13,10 @@ Function Test-AzureResource The Resource Group that the objects will be tested in reference to. .PARAMETER ResourceName - Name of the Azure Resource name. + Name of the Azure Resource. + + .PARAMETER ResourceType + Type of the Azure Resource. .EXAMPLE Test-AzureResource -ResourceGroup "WinGet" -ResourceName "contosorestsource" @@ -27,70 +30,64 @@ Function Test-AzureResource [ValidateSet("Function")] [Parameter(Position=2, Mandatory = $false)] [string]$ResourceType = "Function" ) - BEGIN - { - $Result = $false - $AzureResourceGroupName = $ResourceGroup - $AzureResourceName = $ResourceName - - $AzureResourceGroupNameNullOrWhiteSpace = $false - $AzureResourceGroupNameExists = $false - - $AzureResourceNameNullOrWhiteSpace = $false - $AzureResourceNameExists = $false - - ##Determines if the Azure Resource Group Name is not null or empty - if([string]::IsNullOrWhiteSpace($AzureResourceGroupName)) { - $AzureResourceGroupName = "" - $AzureResourceGroupNameNullOrWhiteSpace = $true - } - if($(Get-AzResourceGroup).Where({$_.ResourceGroupName -eq $AzureResourceGroupName}).Count -gt 0) { - $AzureResourceGroupNameExists = $true - } + $Result = $false + $AzureResourceGroupName = $ResourceGroup + $AzureResourceName = $ResourceName - ## Determines if the Azure Resource Name is not null or empty - if([string]::IsNullOrWhiteSpace($AzureResourceName)) { - $AzureResourceName = "" - $AzureResourceNameNullOrWhiteSpace = $true - } + $AzureResourceGroupNameNullOrWhiteSpace = $false + $AzureResourceGroupNameExists = $false - ## Determines if the Azure Resource Name is in Azure - if ($AzureResourceGroupNameExists) { - switch ($ResourceType) { - "Function" { - if($(Get-AzFunctionApp -ResourceGroupName $AzureResourceGroupName).Where({$_.Name -eq $AzureResourceName}).Count -gt 0) { - $AzureResourceNameExists = $true - } + $AzureResourceNameNullOrWhiteSpace = $false + $AzureResourceNameExists = $false + + ##Determines if the Azure Resource Group Name is not null or empty + if([string]::IsNullOrWhiteSpace($AzureResourceGroupName)) { + $AzureResourceGroupName = "" + $AzureResourceGroupNameNullOrWhiteSpace = $true + } + + if($(Get-AzResourceGroup).Where({$_.ResourceGroupName -eq $AzureResourceGroupName}).Count -gt 0) { + $AzureResourceGroupNameExists = $true + } + + ## Determines if the Azure Resource Name is not null or empty + if([string]::IsNullOrWhiteSpace($AzureResourceName)) { + $AzureResourceName = "" + $AzureResourceNameNullOrWhiteSpace = $true + } + + ## Determines if the Azure Resource Name is in Azure + if ($AzureResourceGroupNameExists) { + switch ($ResourceType) { + "Function" { + if($(Get-AzFunctionApp -ResourceGroupName $AzureResourceGroupName).Where({$_.Name -eq $AzureResourceName}).Count -gt 0) { + $AzureResourceNameExists = $true } } } } - PROCESS - { - $VerboseMessage = "Azure Resources:`n" - $VerboseMessage += " Azure Resource Group Exists: $AzureResourceGroupNameExists`n" - $VerboseMessage += " Azure Resource Exists: $AzureResourceNameExists" - Write-Verbose -Message $VerboseMessage - - ## If either the Azure Resource Name or the Azure Resource Group Name are null, error. - if($AzureResourceGroupNameNullOrWhiteSpace -or $AzureResourceNameNullOrWhiteSpace -or !$AzureResourceGroupNameExists -or !$AzureResourceNameExists) { - $ErrorMessage = "Both the Azure Resource Group and Resource Names can not be null and must exist. Please verify that the Azure Resource Group and Resource exist." - $ErrReturnObject = @{ - AzureResourceGroupNameNullOrWhiteSpace = $AzureResourceGroupNameNullOrWhiteSpace - AzureResourceNameNullOrWhiteSpace = $AzureResourceNameNullOrWhiteSpace - AzureResourceGroupNameExists = $AzureResourceGroupNameExists - AzureResourceNameExists = $AzureResourceNameExists - Result = $false - } - Write-Error -Message $ErrorMessage -Category InvalidArgument -TargetObject $ErrReturnObject + $VerboseMessage = "Azure Resources:`n" + $VerboseMessage += " Azure Resource Group Exists: $AzureResourceGroupNameExists`n" + $VerboseMessage += " Azure Resource Exists: $AzureResourceNameExists" + Write-Verbose -Message $VerboseMessage + + ## If either the Azure Resource Name or the Azure Resource Group Name are null, error. + if($AzureResourceGroupNameNullOrWhiteSpace -or $AzureResourceNameNullOrWhiteSpace -or !$AzureResourceGroupNameExists -or !$AzureResourceNameExists) { + $ErrorMessage = "Both the Azure Resource Group and Resource Names can not be null and must exist. Please verify that the Azure Resource Group and Resource exist." + $ErrReturnObject = @{ + AzureResourceGroupNameNullOrWhiteSpace = $AzureResourceGroupNameNullOrWhiteSpace + AzureResourceNameNullOrWhiteSpace = $AzureResourceNameNullOrWhiteSpace + AzureResourceGroupNameExists = $AzureResourceGroupNameExists + AzureResourceNameExists = $AzureResourceNameExists + Result = $false } - - $Result = $AzureResourceGroupNameExists -and $AzureResourceNameExists - } - END - { - return $Result + + Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject } + + $Result = $AzureResourceGroupNameExists -and $AzureResourceNameExists + + return $Result } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 index f86244d8..184f75d5 100644 --- a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 @@ -35,37 +35,31 @@ Function Test-ConnectionToAzure [Parameter(Position=0, Mandatory=$false)] [string] $SubscriptionName, [Parameter(Position=1, Mandatory=$false)] [string] $SubscriptionId ) - BEGIN - { - $Result = $false - $AzContext = Get-AzContext - } - PROCESS - { - if($AzContext) { - if($SubscriptionName -and $AzContext.Subscription.Name -ne $SubscriptionName) { - ## If Subscription Name paramter is passed in, and the value doesn't match current connection return $false - Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionName" - $Result = $false - } - elseif($SubscriptionId -and $AzContext.Subscription.Id -ne $SubscriptionId) { - ## If Subscription Id paramter is passed in, and the value doesn't match current connection return $false - Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionId" - $Result = $false - } - else { - Write-Information "Connected to Azure" - $Result = $true - } + + $Result = $false + $AzContext = Get-AzContext + + if($AzContext) { + if($SubscriptionName -and $AzContext.Subscription.Name -ne $SubscriptionName) { + ## If Subscription Name paramter is passed in, and the value doesn't match current connection return $false + Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionName" + $Result = $false } - else { - ## Not currently connected to Azure - Write-Error "Not connected to Azure, please connect to your Azure Subscription" + elseif($SubscriptionId -and $AzContext.Subscription.Id -ne $SubscriptionId) { + ## If Subscription Id paramter is passed in, and the value doesn't match current connection return $false + Write-Error "Connection to an unmatched Subscription in Azure. Not connected to $SubscriptionId" $Result = $false } + else { + Write-Information "Connected to Azure" + $Result = $true + } } - END - { - Return $Result + else { + ## Not currently connected to Azure + Write-Error "Not connected to Azure, please connect to your Azure Subscription" + $Result = $false } + + return $Result } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 b/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 index 343380c4..fd831cc3 100644 --- a/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 +++ b/Tools/PowershellModule/src/Library/Test-PowerShellModuleExist.ps1 @@ -31,42 +31,28 @@ Function Test-PowerShellModuleExist [Parameter(Position=0, Mandatory=$true, ParameterSetName="Single")] [string]$Name, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Multiple")] [string[]]$Modules ) - BEGIN - { - ## Validation result to be returned is True until proven otherwise. - $ValidationStatus = $true - $TestResult = @() - } - PROCESS - { - switch ($PsCmdlet.ParameterSetName) { - "Multiple" { - foreach ($RequiredModule in $RequiredModules) { - ## Tests if the module is installed - $Result = Test-PowerShellModuleExist -Name $RequiredModule - $TestResult += @{ - TestedModule = $RequiredModule - ModuleInstalled = $Result - } - - ## Specifies that a module is missing - if(!($Result)) { - $ValidationStatus = $false - Write-Error "Missing required PowerShell modules. Run the following command to install the missing modules: Install-Module $RequiredModule" - } - } - } - "Single" { - ## Determines if the PowerShell Module is installed - if(!$(Get-Module -ListAvailable -Name $RequiredModule) ) { - $ValidationStatus = $false - } + + ## Validation result to be returned is True until proven otherwise. + $ValidationStatus = $true + + switch ($PsCmdlet.ParameterSetName) { + "Multiple" { + foreach ($RequiredModule in $RequiredModules) { + ## Tests if the module is installed + $Result = Test-PowerShellModuleExist -Name $RequiredModule + + $ValidationStatus = $ValidationStatus -and $Result } } + "Single" { + ## Determines if the PowerShell Module is installed + if(!$(Get-Module -ListAvailable -Name $RequiredModule)) { + $ValidationStatus = $false + Write-Warning -Message "Missing required PowerShell modules. Run the following command to install the missing modules: Install-Module $RequiredModule" + } + } } - END - { - ## Returns a value only if the module is missing - Return $ValidationStatus - } + + ## Returns a value only if the module is missing + return $ValidationStatus } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 index 87f17be3..90ed7842 100644 --- a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 @@ -4,67 +4,59 @@ Function Test-WinGetManifest { <# .SYNOPSIS - [Stub for a backlog issue, essentially does nothing today.] + [TODO: Stub for a backlog issue, essentially does nothing today.] .DESCRIPTION + Validates a WinGet Manifest. + .PARAMETER Path + Points to either a folder containing a specific application's manifest of type .json or .yaml or to a specific .json or .yaml file. - .PARAMETER SubscriptionName - - - .PARAMETER SubscriptionId + If you are processing a multi-file manifest, point to the folder that contains all yamls. Note: all yamls within the folder must be part of + the same package manifest. + .PARAMETER Manifest + The WinGetManifest object .EXAMPLE - Test-WinGetManifest -Path "" + Test-WinGetManifest -Path "C:\WinGetManifest" .EXAMPLE - Test-WinGetManifest -Manifest "" + Test-WinGetManifest -Manifest $Manifest #> [CmdletBinding(DefaultParameterSetName = 'File')] PARAM( [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, - [Parameter(Position=0, Mandatory=$true, ParameterSetName="Object")] $Manifest + [Parameter(Position=0, Mandatory=$true, ParameterSetName="Object")] [WinGetManifest]$Manifest ) - BEGIN - { - Write-Verbose -Message "Validating the Manifest ($($PSCmdlet.ParameterSetName))." - - $Return = $true - - switch ($($PSCmdlet.ParameterSetName)) { - "File"{ - ## Convert to full path if applicable - $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) - - $PathFound = Test-Path -Path $Path; - - if ($PathFound) - { - ## Construct $Manifest from path then validate - } - else - { - Write-Error "Manifest path not found: $Path" - $Return = $false; - } + + $Return = $false + + switch ($($PSCmdlet.ParameterSetName)) { + "File"{ + ## Convert to full path if applicable + $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) + + $PathFound = Test-Path -Path $Path; + if ($PathFound) + { + ## Construct $Manifest from path then validate + $Return = $true; } - "Object" { + else + { + Write-Error "Manifest path not found: $Path" } } - - + "Object" { + ## Validate manifest + $Return = $true; + } } - PROCESS - { - } - END - { - Write-Information "Testing the Manifest has passed: $Return" - return $Return - } + Write-Information "Testing the Manifest has passed: $Return" + return $Return } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 index 6d8a9af3..cd5d45ea 100644 --- a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +## Classes representing WinGet manifest + class WinGetAgreement { [string]$AgreementLabel diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index 3059c1d9..7ed4111a 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -31,7 +31,7 @@ # Required modules. # Due to issue https://github.com/PowerShell/PowerShell/issues/11190, using RequiredModules will greatly slow down the import module. We'll handle them manually in psm1. - # RequiredModules = @('Az', 'powershell-yaml') + # RequiredModules = @('Az') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource") diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index 83edb066..cf9139ff 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -20,7 +20,7 @@ catch { Get-ChildItem -Path "$PSScriptRoot\Library" -Filter *.ps1 | foreach-object { . $_.FullName } ## Validates that the required Azure Modules are present when the script is imported. -[string[]]$RequiredModules = @("Az", "powershell-yaml") +[string[]]$RequiredModules = @("Az") foreach ($RequiredModule in $RequiredModules) { ## Tests if the module is installed diff --git a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs index 0b1fb2fb..c7a7e258 100644 --- a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs +++ b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs @@ -16,6 +16,7 @@ namespace Microsoft.WinGet.RestSource.PowershellSupport using Microsoft.WinGetUtil.Common; using Microsoft.WinGetUtil.Models.V1; using Newtonsoft.Json; + using static System.Runtime.InteropServices.JavaScript.JSType; /// /// Supports converting yaml manifest to json string. @@ -26,36 +27,47 @@ public static class YamlToRestConverter /// /// Processes a directory for yaml manifests and converts it into the rest json format. /// - /// Directory to process. Should contain the manifests for a single app. + /// Manifest file or directory to process. Should contain manifests for a single app. /// Prior json data to merge with. /// A string of the rest source json. public static string AddManifestToPackageManifest( - string directory, + string path, string priorRestManifest) { - // Construct merged manifest - var packageFiles = Directory.GetFiles(directory); - string mergedManifestFilePath = - Path.Combine( - Path.GetTempPath(), - Path.GetRandomFileName() + ".yaml"); - if (packageFiles.Length > 1) + string manifestPath = string.Empty; + + if (File.Exists(path)) + { + manifestPath = path; + } + else if (Directory.Exists(path)) { - // Create merged manifest from multifile manifests. - var factory = new WinGetFactory(); - using var manifestResult = factory.CreateManifest(directory, mergedManifestFilePath, WinGetCreateManifestOption.NoValidation); + var packageFiles = Directory.GetFiles(path); - if (!manifestResult.IsValid) + if (packageFiles.Length > 1) + { + manifestPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".yaml"); + + // Create merged manifest from multifile manifests. + var factory = new WinGetFactory(); + using var manifestResult = factory.CreateManifest(path, manifestPath, WinGetCreateManifestOption.NoValidation); + + if (!manifestResult.IsValid) + { + throw new ArgumentException("Unable to create merged manifest from multifile manifests."); + } + } + else { - throw new Exception("Unable to create merged manifest from multifile manifests."); + manifestPath = packageFiles.First(); } } else { - mergedManifestFilePath = packageFiles.First(); + throw new ArgumentException("Input manifest path not found."); } - Manifest manifest = Manifest.CreateManifestFromPath(mergedManifestFilePath); + Manifest manifest = Manifest.CreateManifestFromPath(manifestPath); // Convert the manifest into a rest manifestPost format and merge with any existing data. PackageManifest packageManifest = PackageManifestUtils.AddManifestToPackageManifest( diff --git a/src/WinGet.RestSource.sln b/src/WinGet.RestSource.sln index 2cd93498..ffdf1c8f 100644 --- a/src/WinGet.RestSource.sln +++ b/src/WinGet.RestSource.sln @@ -81,7 +81,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{5DF7 ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 ..\Tools\PowershellModule\src\Library\New-WinGetSource.ps1 = ..\Tools\PowershellModule\src\Library\New-WinGetSource.ps1 ..\Tools\PowershellModule\src\Library\Remove-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Remove-WinGetManifest.ps1 - ..\Tools\PowershellModule\src\Library\Test-ARMResourceName.ps1 = ..\Tools\PowershellModule\src\Library\Test-ARMResourceName.ps1 ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 = ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 = ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 = ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 diff --git a/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs b/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs index 88e2d34e..c0a93438 100644 --- a/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs +++ b/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs @@ -442,11 +442,11 @@ public async Task> GetPackageManifests(string packa // Apply Channel Filter if (!string.IsNullOrEmpty(channelFilter)) { - foreach (PackageManifest pm in apiDataDocument.Items) + foreach (PackageManifest packageManifest in apiDataDocument.Items) { - if (pm.Versions != null) + if (packageManifest.Versions != null) { - pm.Versions = new VersionsExtended(pm.Versions.Where(extended => extended.Channel != null && extended.Channel.Equals(channelFilter))); + packageManifest.Versions = new VersionsExtended(packageManifest.Versions.Where(extended => extended.Channel != null && extended.Channel.Equals(channelFilter))); } } } From f53f075fcc2fde7509b5663ff070ac14dba533ea Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:44:56 -0800 Subject: [PATCH 08/16] Find package --- .../src/Library/Add-WinGetManifest.ps1 | 11 +- .../src/Library/Connect-ToAzure.ps1 | 4 +- .../src/Library/Find-WinGetManifest.ps1 | 185 ++++++++++++++++++ .../src/Library/Get-WinGetManifest.ps1 | 18 +- .../src/Library/New-ARMParameterObject.ps1 | 10 +- .../src/Library/Remove-WinGetManifest.ps1 | 10 +- .../src/Library/Test-ConnectionToAzure.ps1 | 4 +- .../src/Microsoft.WinGet.Source.psd1 | 2 +- .../src/Microsoft.WinGet.Source.psm1 | 17 ++ src/WinGet.RestSource.sln | 1 + 10 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index 77429f48..c6d28651 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -115,10 +115,9 @@ Function Add-WinGetManifest Write-Verbose -Message "Contents of manifest have been retrieved. Package Identifier: $($Manifest.PackageIdentifier)." Write-Verbose -Message "Confirming that the Package ID doesn't already exist in Azure for $($Manifest.PackageIdentifier)." - $ApiHeader.Remove("x-functions-key") - $ApiHeader.Add("x-functions-key", $FunctionKeyGet) + $ApiHeader.Add["x-functions-key"] = $FunctionKeyGet $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier - $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethodGet -ContentType $ApiContentType -ErrorVariable ErrorInvoke + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethodGet -ErrorVariable ErrorInvoke if ($ErrorInvoke) { ## No existing manifest retrieved, submit as new manifest @@ -126,8 +125,7 @@ Function Add-WinGetManifest $ApiMethod = $ApiMethodPost $AzFunctionURL = $AzFunctionURLBase - $ApiHeader.Remove("x-functions-key") - $ApiHeader.Add("x-functions-key", $FunctionKeyPost) + $ApiHeader["x-functions-key"] = $FunctionKeyPost } else { ## Existing manifest retrieved, submit as update existing manifest @@ -140,8 +138,7 @@ Function Add-WinGetManifest $ApiMethod = $ApiMethodPut $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier - $ApiHeader.Remove("x-functions-key") - $ApiHeader.Add("x-functions-key", $FunctionKeyPut) + $ApiHeader["x-functions-key"] = $FunctionKeyPut } Write-Verbose -Message "The Manifest will be added using the $ApiMethod REST API." diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index 08fc9c82..b8ee708b 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -38,8 +38,8 @@ Function Connect-ToAzure #> PARAM( - [Parameter(Position=0, Mandatory=$false)] [string]$SubscriptionName, - [Parameter(Position=1, Mandatory=$false)] [string]$SubscriptionId + [Parameter(Position=0, Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Position=1, Mandatory=$false)] [string]$SubscriptionId = "" ) $TestAzureConnection = $false diff --git a/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 new file mode 100644 index 00000000..41f58aaa --- /dev/null +++ b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 @@ -0,0 +1,185 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Function Find-WinGetManifest +{ + <# + .SYNOPSIS + Connects to the specified source REST API to retrieve the package manifests, returning manifest package identifier, name, publisher and versions. + + .DESCRIPTION + Connects to the specified source REST API to retrieve the package manifests, returning manifest package identifier, name, publisher and versions. + This function does not return the full WinGetManifest since the results may be very large. Use Get-WinGetManifest for retrieving full WinGetManifest. + + .PARAMETER FunctionName + Name of the Azure Function Name that contains the Windows Package Manager REST APIs. + + .PARAMETER Query + [Optional] The query to be performed against rest source. Empty query will return all package manifests. + + .PARAMETER PackageIdentifier + [Optional] Filter the search results with PackageIdentifier + + .PARAMETER PackageName + [Optional] Filter the search results with PackageName + + .PARAMETER SubscriptionName + [Optional] Name of the Azure Subscription that contains the Azure Function which contains the REST APIs. + + .PARAMETER Exact + [Optional] If specified, the rest source search will be performed with exact match. + + .EXAMPLE + Find-WinGetManifest -FunctionName "contosorestsource" -Query "PowerToys" + + Searches rest source for package manifests with PowerToys. + + .EXAMPLE + Find-WinGetManifest -FunctionName "contosorestsource" -Query "PowerToys" -PackageIdentifier "Windows.PowerToys" + + Searches rest source for package manifests with PowerToys and filter the result with package identifier Windows.PowerToys. + + .EXAMPLE + Find-WinGetManifest -FunctionName "contosorestsource" -Query "PowerToys" -PackageName "Windows PowerToys" -Exact + + Searches rest source for package manifests with PowerToys and filter the result with package name Windows PowerToys. Use exact match. + + #> + PARAM( + [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, + [Parameter(Position=1, Mandatory=$false)] [string]$Query = "", + [Parameter(Position=2, Mandatory=$false)] [string]$PackageIdentifier = "", + [Parameter(Position=3, Mandatory=$false)] [string]$PackageName = "", + [Parameter(Position=4, Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter()] [switch]$Exact + ) + BEGIN + { + [PSCustomObject[]] $Return = @() + + ############################### + ## Connects to Azure, if not already connected. + Write-Verbose "Testing connection to Azure." + $Result = Connect-ToAzure -SubscriptionName $SubscriptionName + if(!($Result)) { + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." -ErrorAction Stop + } + + ############################### + ## Gets Resource Group name of the Azure Function + Write-Verbose -Message "Determines the Azure Function Resource Group Name" + $ResourceGroupName = $(Get-AzFunctionApp).Where({$_.Name -eq $FunctionName}).ResourceGroupName + if(!$ResourceGroupName) { + Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop + } + + ############################### + ## Verify Azure Resources Exist + Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." + $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName + if(!$Result) { + Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop + } + + ## Retrieves the Azure Function URL used to add new manifests to the REST source + Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." + $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + + $FunctionAppId = $FunctionApp.Id + $DefaultHostName = $FunctionApp.DefaultHostName + + $TriggerName = "ManifestSearchPost" + $ApiContentType = "application/json" + $ApiMethod = "Post" + + ## Creates the API Post Header + $ApiHeader = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" + $ApiHeader.Add("Accept", 'application/json') + $FunctionKey = (Invoke-AzResourceAction -ResourceId "$FunctionAppId/functions/$TriggerName" -Action listkeys -Force).default + $ApiHeader.Add("x-functions-key", $FunctionKey) + + $AzFunctionURL = "https://" + $DefaultHostName + "/api/manifestSearch" + } + PROCESS + { + Write-Verbose -Message "Invoking the REST API call." + + $RequestBody = @{ + Query = @{ + KeyWord = $Query + MatchType = $Exact ? "Exact" : "Substring" + } + Filters = @() + } + + if (![string]::IsNullOrWhiteSpace($PackageIdentifier)) { + $RequestBody.Filters += @{ + PackageMatchField = "PackageIdentifier" + RequestMatch = @{ + KeyWord = $PackageIdentifier + MatchType = $Exact ? "Exact" : "CaseInsensitive" + } + } + } + if (![string]::IsNullOrWhiteSpace($PackageName)) { + $RequestBody.Filters += @{ + PackageMatchField = "PackageName" + RequestMatch = @{ + KeyWord = $PackageName + MatchType = $Exact ? "Exact" : "CaseInsensitive" + } + } + } + + $RequestBodyJson = ConvertTo-Json $RequestBody -Depth 8 -Compress + Write-Verbose "Search Request: $RequestBodyJson" + + $ContinuationToken = $null + do { + if ($ContinuationToken) { + $ApiHeader["ContinuationToken"] = $ContinuationToken + } + + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -Body $RequestBodyJson -ContentType $ApiContentType -ErrorVariable ErrorInvoke + + if ($ErrorInvoke) { + $ErrorMessage = "Failed to get search result from $FunctionName. Verify the information you provided and try again." + $ErrReturnObject = @{ + AzFunctionURL = $AzFunctionURL + ApiMethod = $ApiMethod + ApiContentType = $ApiContentType + Response = $Response + InvokeError = $ErrorInvoke + } + + Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject + return + } + else { + Write-Verbose "Found ($($Response.Data.Count)) Manifests that matched." + + foreach ($ResponseData in $Response.Data) { + Write-Verbose -Message "Parsing through the returned results: $ResponseData" + $ManifestInfo = [PSCustomObject]@{ + PackageIdentifier = $ResponseData.PackageIdentifier + PackageName = $ResponseData.PackageName + Publisher = $ResponseData.Publisher + Versions = [string[]]@() + } + foreach ($Version in $ResponseData.Versions) { + $ManifestInfo.Versions += $Version.PackageVersion + } + $Return += $ManifestInfo + } + } + + $ContinuationToken = $Response.ContinuationToken + } while (![string]::IsNullOrWhiteSpace($ContinuationToken)) + } + END + { + ## Returns results + Write-Verbose -Message "Returning ($($Return.Count)) manifests based on search." + return $Return + } +} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 2710bb8d..0e462b29 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -26,7 +26,7 @@ Function Get-WinGetManifest Name of the Azure Function Name that contains the Windows Package Manager REST APIs. .PARAMETER PackageIdentifier - [Optional] Supports input from pipeline. The Windows Package Manager Package Identifier of a specific Package Manifest result. + Supports input from pipeline. The Windows Package Manager Package Identifier of a specific Package Manifest result. .PARAMETER SubscriptionName [Optional] Name of the Azure Subscription that contains the Azure Function which contains the REST APIs. @@ -60,10 +60,10 @@ Function Get-WinGetManifest [CmdletBinding(DefaultParameterSetName = 'Azure')] PARAM( [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, - [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$JSON, + [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$JSON = $null, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Azure")] [string]$FunctionName, - [Parameter(Position=1, Mandatory=$false,ParameterSetName="Azure", ValueFromPipeline=$true)] [string]$PackageIdentifier, - [Parameter(Position=2, Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName + [Parameter(Position=1, Mandatory=$true, ParameterSetName="Azure", ValueFromPipeline=$true)][ValidateNotNullOrEmpty()] [string]$PackageIdentifier, + [Parameter(Position=2, Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName = "" ) BEGIN { @@ -80,7 +80,7 @@ Function Get-WinGetManifest if(!($Result)) { Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." -ErrorAction Stop } - + ############################### ## Gets Resource Group name of the Azure Function Write-Verbose -Message "Determines the Azure Function Resource Group Name" @@ -88,7 +88,7 @@ Function Get-WinGetManifest if(!$ResourceGroupName) { Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - + ############################### ## Verify Azure Resources Exist Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." @@ -103,9 +103,8 @@ Function Get-WinGetManifest $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName - + $TriggerName = "ManifestGet" - $ApiContentType = "application/json" $ApiMethod = "Get" ## Creates the API Post Header @@ -128,14 +127,13 @@ Function Get-WinGetManifest ## Publishes the Manifest to the Windows Package Manager REST source Write-Verbose -Message "Invoking the REST API call." - $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType -ErrorVariable ErrorInvoke + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ErrorVariable ErrorInvoke if($ErrorInvoke) { $ErrorMessage = "Failed to get Manifest from $FunctionName. Verify the information you provided and try again." $ErrReturnObject = @{ AzFunctionURL = $AzFunctionURL ApiMethod = $ApiMethod - ApiContentType = $ApiContentType Response = $Response InvokeError = $ErrorInvoke } diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 index 809aaaf1..8550f3a6 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 @@ -472,10 +472,10 @@ Function New-ARMParameterObject appServiceName = @{ value = $aspName } # Azure App Service Name keyVaultName = @{ value = $KeyVaultName } # Azure Keyvault Name appInsightName = @{ value = $AppInsightsName } # Azure App Insights Name - manifestCacheEndpoint = @{ value = $manifestCacheEndpoint } # unknown - monitoringTenant = @{ value = $monitoringTenant } # unknown - monitoringRole = @{ value = $monitoringRole } # unknown - monitoringMetricsAccount = @{ value = $monitoringMetricsAccount } # unknown + manifestCacheEndpoint = @{ value = $manifestCacheEndpoint } # Not suported + monitoringTenant = @{ value = $monitoringTenant } # Not suported + monitoringRole = @{ value = $monitoringRole } # Not suported + monitoringMetricsAccount = @{ value = $monitoringMetricsAccount } # Not suported } } } @@ -488,7 +488,7 @@ Function New-ARMParameterObject foreach ($object in $ARMObjects) { ## Converts the structure of the variable to a JSON file. Write-Verbose -Message " Creating the Parameter file for $($Object.ObjectType) in the following location:`n $($Object.ParameterPath)" - $parameterFile = $Object.Parameters | ConvertTo-Json -Depth 7 + $parameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 $parameterFile| Out-File -FilePath $Object.ParameterPath -Force Write-Verbose -Message "Parameter file: ($parameterFile)" } diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index dc04e68b..c652dac6 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -71,7 +71,6 @@ Function Remove-WinGetManifest $TriggerNameManifestDelete = "ManifestDelete" $TriggerNameVersionDelete = "VersionDelete" - $ApiContentType = "application/json" $ApiMethod = "Delete" $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName @@ -95,23 +94,20 @@ Function Remove-WinGetManifest $AzFunctionURL = $AzFunctionURLBase if([string]::IsNullOrWhiteSpace($PackageVersion)) { $AzFunctionURL += "packageManifests/" + $PackageIdentifier - $ApiHeader.Remove("x-functions-key") - $ApiHeader.Add("x-functions-key", $FunctionKeyManifestDelete) + $ApiHeader["x-functions-key"] = $FunctionKeyManifestDelete } else { $AzFunctionURL += "packages/" + $PackageIdentifier + "/versions/" + $PackageVersion - $ApiHeader.Remove("x-functions-key") - $ApiHeader.Add("x-functions-key", $FunctionKeyVersionDelete) + $ApiHeader["x-functions-key"] = $FunctionKeyVersionDelete } - $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ContentType $ApiContentType -ErrorVariable ErrorInvoke + $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethod -ErrorVariable ErrorInvoke if($ErrorInvoke) { $ErrorMessage = "Failed to remove Manifest from $FunctionName. Verify the information you provided and try again." $ErrReturnObject = @{ AzFunctionURL = $AzFunctionURL ApiMethod = $ApiMethod - ApiContentType = $ApiContentType Response = $Response InvokeError = $ErrorInvoke } diff --git a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 index 184f75d5..739526ec 100644 --- a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 @@ -32,8 +32,8 @@ Function Test-ConnectionToAzure #> PARAM( - [Parameter(Position=0, Mandatory=$false)] [string] $SubscriptionName, - [Parameter(Position=1, Mandatory=$false)] [string] $SubscriptionId + [Parameter(Position=0, Mandatory=$false)] [string] $SubscriptionName = "", + [Parameter(Position=1, Mandatory=$false)] [string] $SubscriptionId = "" ) $Result = $false diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index 7ed4111a..f271059c 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -34,7 +34,7 @@ # RequiredModules = @('Az') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource") + FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Find-WinGetManifest") # Cmdlets to export from this module, for best performance, do not use wild cards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index cf9139ff..d76293b1 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -6,6 +6,23 @@ ## Loads the binaries from the Desktop App Installer Library - Only if running PowerShell at a specified edition try { + $architecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture + + switch ($architecture) { + "X64" { + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + } + "X86" { + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x86\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + } + "Arm64" { + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-arm64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + } + Default { + throw "Powershell Core runtime architecture not supported" + } + } + Add-Type -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\Microsoft.Winget.PowershellSupport.dll" } catch { diff --git a/src/WinGet.RestSource.sln b/src/WinGet.RestSource.sln index ffdf1c8f..61fa72c0 100644 --- a/src/WinGet.RestSource.sln +++ b/src/WinGet.RestSource.sln @@ -75,6 +75,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{5DF7 ..\Tools\PowershellModule\src\Library\Add-AzureResourceGroup.ps1 = ..\Tools\PowershellModule\src\Library\Add-AzureResourceGroup.ps1 ..\Tools\PowershellModule\src\Library\Add-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Add-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\Connect-ToAzure.ps1 = ..\Tools\PowershellModule\src\Library\Connect-ToAzure.ps1 + ..\Tools\PowershellModule\src\Library\Find-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Find-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\Get-PairedAzureRegion.ps1 = ..\Tools\PowershellModule\src\Library\Get-PairedAzureRegion.ps1 ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 From 72bc24c9864e47012de1ca9cc340a9dbb5bdb198 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sat, 30 Nov 2024 14:55:54 -0800 Subject: [PATCH 09/16] Update manifest merge code --- .../WinGet.RestSource.PowershellSupport.csproj | 2 +- src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs | 4 ++-- src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj index 5811e19c..1048287d 100644 --- a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj +++ b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj @@ -44,7 +44,7 @@ - + diff --git a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs index ef4896b5..3adf7396 100644 --- a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs +++ b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs @@ -123,9 +123,9 @@ public static PackageManifest AddManifestToPackageManifest( newInstaller.ProductCode = installer.ProductCode ?? manifest.ProductCode; newInstaller.Capabilities = installer.Capabilities.ToApiArray() ?? manifest.Capabilities.ToApiArray(); newInstaller.RestrictedCapabilities = installer.RestrictedCapabilities.ToApiArray() ?? manifest.RestrictedCapabilities.ToApiArray(); - newInstaller.DownloadCommandProhibited = installer.DownloadCommandProhibited; + newInstaller.DownloadCommandProhibited = installer.DownloadCommandProhibited ?? manifest.DownloadCommandProhibited ?? false; newInstaller.RepairBehavior = installer.RepairBehavior ?? manifest.RepairBehavior; - newInstaller.ArchiveBinariesDependOnPath = installer.ArchiveBinariesDependOnPath; + newInstaller.ArchiveBinariesDependOnPath = installer.ArchiveBinariesDependOnPath ?? manifest.ArchiveBinariesDependOnPath ?? false; newInstaller.InstallerIdentifier = string.Join("_", newInstaller.Architecture, newInstaller.InstallerLocale, newInstaller.Scope, Guid.NewGuid()); versionExtended.AddInstaller(newInstaller); diff --git a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj index 7caa2968..21c987fc 100644 --- a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj +++ b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj @@ -42,7 +42,7 @@ - + From 36f7889799b5f7e62455f2cd45dc9ca210a3fd18 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sun, 1 Dec 2024 03:19:31 -0800 Subject: [PATCH 10/16] update utils manifest --- .../ExtendedSchemas/VersionsExtended.cs | 8 +- .../Models/Schemas/DefaultLocale.cs | 4 +- .../Models/Schemas/Installer.cs | 14 +- .../Models/Schemas/Installers.cs | 4 +- .../Models/Schemas/Locale.cs | 8 +- .../Models/Schemas/Locales.cs | 12 +- .../Utils/PackageManifestUtils.cs | 239 +++++++++++++++++- ...Notes.cs => InstallationNotesValidator.cs} | 10 +- 8 files changed, 256 insertions(+), 43 deletions(-) rename src/WinGet.RestSource.Utils/Validators/StringValidators/{InstallationNotes.cs => InstallationNotesValidator.cs} (69%) diff --git a/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionsExtended.cs b/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionsExtended.cs index 17f02b85..88b606b1 100644 --- a/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionsExtended.cs +++ b/src/WinGet.RestSource.Utils/Models/ExtendedSchemas/VersionsExtended.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -52,7 +52,7 @@ public VersionsExtended(IEnumerable enumerable) ApiDataValidator.NotNull(obj); // Verify Version does not exists - this.AssertVersionDoesNotExists(obj.PackageVersion); + this.AssertVersionDoesNotExist(obj.PackageVersion); // Add base.Add(obj); @@ -100,7 +100,7 @@ public void Add(Version obj) ApiDataValidator.NotNull(obj); // Verify Version does not exists - this.AssertVersionDoesNotExists(obj.PackageVersion); + this.AssertVersionDoesNotExist(obj.PackageVersion); // Add base.Add(new VersionExtended(obj)); @@ -333,7 +333,7 @@ private void AssertVersionExists(string version) } } - private void AssertVersionDoesNotExists(string version) + private void AssertVersionDoesNotExist(string version) { if (this.VersionExists(version)) { diff --git a/src/WinGet.RestSource.Utils/Models/Schemas/DefaultLocale.cs b/src/WinGet.RestSource.Utils/Models/Schemas/DefaultLocale.cs index b250d0da..201cc38a 100644 --- a/src/WinGet.RestSource.Utils/Models/Schemas/DefaultLocale.cs +++ b/src/WinGet.RestSource.Utils/Models/Schemas/DefaultLocale.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -115,7 +115,7 @@ public bool Equals(DefaultLocale other) return true; } - return Equals(this, other) && Equals(this.Moniker, other.Moniker); + return base.Equals(other) && Equals(this.Moniker, other.Moniker); } /// diff --git a/src/WinGet.RestSource.Utils/Models/Schemas/Installer.cs b/src/WinGet.RestSource.Utils/Models/Schemas/Installer.cs index dab2cc20..63f374e7 100644 --- a/src/WinGet.RestSource.Utils/Models/Schemas/Installer.cs +++ b/src/WinGet.RestSource.Utils/Models/Schemas/Installer.cs @@ -181,23 +181,23 @@ public Installer(Installer installer) /// Gets or sets a value indicating whether InstallerAbortsTerminal is set. /// This indicates if the package aborts the terminal during installation. /// - public bool InstallerAbortsTerminal { get; set; } + public bool? InstallerAbortsTerminal { get; set; } /// /// Gets or sets ReleaseDate. /// [ReleaseDateValidator] - public DateTime ReleaseDate { get; set; } + public DateTime? ReleaseDate { get; set; } /// /// Gets or sets a value indicating whether the installer requires an install location provided. /// - public bool InstallLocationRequired { get; set; } + public bool? InstallLocationRequired { get; set; } /// /// Gets or sets a value indicating whether the installer requires explicit upgrade. /// - public bool RequireExplicitUpgrade { get; set; } + public bool? RequireExplicitUpgrade { get; set; } /// /// Gets or sets the installer's elevation requirement. @@ -234,7 +234,7 @@ public Installer(Installer installer) /// /// Gets or sets a value indicating whether displayInstallWarnings. /// - public bool DisplayInstallWarnings { get; set; } + public bool? DisplayInstallWarnings { get; set; } /// /// Gets or sets unsupportedArguments. @@ -249,7 +249,7 @@ public Installer(Installer installer) /// /// Gets or sets a value indicating whether the installer is prohibited from being downloaded for offline installation. /// - public bool DownloadCommandProhibited { get; set; } + public bool? DownloadCommandProhibited { get; set; } /// /// Gets or sets RepairBehavior. @@ -260,7 +260,7 @@ public Installer(Installer installer) /// /// Gets or sets a value indicating whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages. /// - public bool ArchiveBinariesDependOnPath { get; set; } + public bool? ArchiveBinariesDependOnPath { get; set; } /// /// Operator==. diff --git a/src/WinGet.RestSource.Utils/Models/Schemas/Installers.cs b/src/WinGet.RestSource.Utils/Models/Schemas/Installers.cs index 7884c8f4..4365c65b 100644 --- a/src/WinGet.RestSource.Utils/Models/Schemas/Installers.cs +++ b/src/WinGet.RestSource.Utils/Models/Schemas/Installers.cs @@ -51,7 +51,7 @@ public Installers(IEnumerable enumerable) ApiDataValidator.NotNull(obj); // Verify installer does not exist - this.AssertInstallerDoesNotExists(obj.InstallerIdentifier); + this.AssertInstallerDoesNotExist(obj.InstallerIdentifier); base.Add(obj); } @@ -150,7 +150,7 @@ private void AssertInstallerExists(string installerIdentifier) } } - private void AssertInstallerDoesNotExists(string installerIdentifier) + private void AssertInstallerDoesNotExist(string installerIdentifier) { if (this.InstallerExists(installerIdentifier)) { diff --git a/src/WinGet.RestSource.Utils/Models/Schemas/Locale.cs b/src/WinGet.RestSource.Utils/Models/Schemas/Locale.cs index daf2deaf..4c0ec8ad 100644 --- a/src/WinGet.RestSource.Utils/Models/Schemas/Locale.cs +++ b/src/WinGet.RestSource.Utils/Models/Schemas/Locale.cs @@ -154,7 +154,8 @@ public Locale(Locale locale) /// /// Gets or sets installationNotes. /// - public InstallationNotes InstallationNotes { get; set; } + [InstallationNotesValidator] + public string InstallationNotes { get; set; } /// /// Gets or sets documentations. @@ -231,11 +232,6 @@ public virtual IEnumerable Validate(ValidationContext validati ApiDataValidator.Validate(this.Agreements, results); } - if (this.InstallationNotes != null) - { - ApiDataValidator.Validate(this.InstallationNotes, results); - } - if (this.Documentations != null) { ApiDataValidator.Validate(this.Documentations, results); diff --git a/src/WinGet.RestSource.Utils/Models/Schemas/Locales.cs b/src/WinGet.RestSource.Utils/Models/Schemas/Locales.cs index 31da7a27..3a36746b 100644 --- a/src/WinGet.RestSource.Utils/Models/Schemas/Locales.cs +++ b/src/WinGet.RestSource.Utils/Models/Schemas/Locales.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -51,7 +51,7 @@ public Locales(IEnumerable enumerable) ApiDataValidator.NotNull(obj); // Verify locale does not exist - this.AssertLocaleDoesNotExists(obj.PackageLocale); + this.AssertLocaleDoesNotExist(obj.PackageLocale); base.Add(obj); } @@ -138,9 +138,9 @@ private bool LocaleExists(string packageLocale) return this.Any(p => p.PackageLocale == packageLocale); } - private void AssertLocaleExists(string installerIdentifier) + private void AssertLocaleExists(string packageLocale) { - if (!this.LocaleExists(installerIdentifier)) + if (!this.LocaleExists(packageLocale)) { throw new InvalidArgumentException( new InternalRestError( @@ -149,9 +149,9 @@ private void AssertLocaleExists(string installerIdentifier) } } - private void AssertLocaleDoesNotExists(string installerIdentifier) + private void AssertLocaleDoesNotExist(string packageLocale) { - if (this.LocaleExists(installerIdentifier)) + if (this.LocaleExists(packageLocale)) { throw new InvalidArgumentException( new InternalRestError( diff --git a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs index 3adf7396..f9220f4c 100644 --- a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs +++ b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs @@ -8,6 +8,8 @@ namespace Microsoft.WinGet.RestSource.Utils { using System; using System.Collections.Generic; + using System.Diagnostics; + using Microsoft.AspNetCore.Http; using Microsoft.WinGet.RestSource.Utils.Models.Arrays; using Microsoft.WinGet.RestSource.Utils.Models.Core; using Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas; @@ -49,7 +51,7 @@ public static PackageManifest AddManifestToPackageManifest( versionExtended.Channel = manifest.Channel; versionExtended.DefaultLocale = new DefaultLocale(); - versionExtended.DefaultLocale.Moniker = string.IsNullOrWhiteSpace(manifest.Moniker) ? null : manifest.Moniker; + versionExtended.DefaultLocale.Moniker = manifest.Moniker; versionExtended.DefaultLocale.PackageLocale = manifest.PackageLocale; versionExtended.DefaultLocale.Publisher = manifest.Publisher; versionExtended.DefaultLocale.PublisherUrl = manifest.PublisherUrl; @@ -64,8 +66,14 @@ public static PackageManifest AddManifestToPackageManifest( versionExtended.DefaultLocale.CopyrightUrl = manifest.CopyrightUrl; versionExtended.DefaultLocale.ShortDescription = manifest.ShortDescription; versionExtended.DefaultLocale.Description = manifest.Description; - versionExtended.DefaultLocale.Tags = manifest.Tags.ToApiArray(); + versionExtended.DefaultLocale.ReleaseNotes = manifest.ReleaseNotes; + versionExtended.DefaultLocale.ReleaseNotesUrl = manifest.ReleaseNotesUrl; + versionExtended.DefaultLocale.Agreements = AddAgreements(manifest.Agreements); + versionExtended.DefaultLocale.PurchaseUrl = manifest.PurchaseUrl; + versionExtended.DefaultLocale.InstallationNotes = manifest.InstallationNotes; + versionExtended.DefaultLocale.Documentations = AddDocumentations(manifest.Documentations); + versionExtended.DefaultLocale.Icons = AddIcons(manifest.Icons); if (manifest.Localization != null) { @@ -86,8 +94,14 @@ public static PackageManifest AddManifestToPackageManifest( newLocale.CopyrightUrl = localization.CopyrightUrl; newLocale.ShortDescription = localization.ShortDescription; newLocale.Description = localization.Description; - newLocale.Tags = localization.Tags.ToApiArray(); + newLocale.ReleaseNotes = localization.ReleaseNotes; + newLocale.ReleaseNotesUrl = localization.ReleaseNotesUrl; + newLocale.Agreements = AddAgreements(localization.Agreements); + newLocale.PurchaseUrl = localization.PurchaseUrl; + newLocale.InstallationNotes = localization.InstallationNotes; + newLocale.Documentations = AddDocumentations(localization.Documentations); + newLocale.Icons = AddIcons(localization.Icons); versionExtended.AddLocale(newLocale); } @@ -104,6 +118,7 @@ public static PackageManifest AddManifestToPackageManifest( newInstaller.Architecture = installer.Arch; newInstaller.InstallerSha256 = installer.Sha256; newInstaller.SignatureSha256 = installer.SignatureSha256; + newInstaller.MSStoreProductIdentifier = installer.ProductId; // Properties present on root node which can be overridden by installer node newInstaller.InstallerLocale = installer.InstallerLocale ?? manifest.InstallerLocale; @@ -114,6 +129,7 @@ public static PackageManifest AddManifestToPackageManifest( newInstaller.InstallModes = installer.InstallModes.ToApiArray() ?? manifest.InstallModes.ToApiArray(); newInstaller.InstallerSwitches = AddInstallerSwitches(installer.Switches) ?? AddInstallerSwitches(manifest.Switches); newInstaller.InstallerSuccessCodes = installer.InstallerSuccessCodes.ToApiArray() ?? manifest.InstallerSuccessCodes.ToApiArray(); + newInstaller.ExpectedReturnCodes = AddExpectedReturnCodes(installer.ExpectedReturnCodes) ?? AddExpectedReturnCodes(manifest.ExpectedReturnCodes); newInstaller.UpgradeBehavior = installer.UpgradeBehavior ?? manifest.UpgradeBehavior; newInstaller.Commands = installer.Commands.ToApiArray() ?? manifest.Commands.ToApiArray(); newInstaller.Protocols = installer.Protocols.ToApiArray() ?? manifest.Protocols.ToApiArray(); @@ -123,9 +139,22 @@ public static PackageManifest AddManifestToPackageManifest( newInstaller.ProductCode = installer.ProductCode ?? manifest.ProductCode; newInstaller.Capabilities = installer.Capabilities.ToApiArray() ?? manifest.Capabilities.ToApiArray(); newInstaller.RestrictedCapabilities = installer.RestrictedCapabilities.ToApiArray() ?? manifest.RestrictedCapabilities.ToApiArray(); - newInstaller.DownloadCommandProhibited = installer.DownloadCommandProhibited ?? manifest.DownloadCommandProhibited ?? false; + newInstaller.InstallerAbortsTerminal = installer.InstallerAbortsTerminal ?? manifest.InstallerAbortsTerminal; + newInstaller.ReleaseDate = TryParseDateTime(installer.ReleaseDate) ?? TryParseDateTime(manifest.ReleaseDate); + newInstaller.InstallLocationRequired = installer.InstallLocationRequired ?? manifest.InstallLocationRequired; + newInstaller.RequireExplicitUpgrade = installer.RequireExplicitUpgrade ?? manifest.RequireExplicitUpgrade; + newInstaller.ElevationRequirement = installer.ElevationRequirement ?? manifest.ElevationRequirement; + newInstaller.UnsupportedOSArchitectures = installer.UnsupportedOSArchitectures.ToApiArray() ?? manifest.UnsupportedOSArchitectures.ToApiArray(); + newInstaller.AppsAndFeaturesEntries = AddAppsAndFeaturesEntries(installer.AppsAndFeaturesEntries) ?? AddAppsAndFeaturesEntries(manifest.AppsAndFeaturesEntries); + newInstaller.Markets = AddMarkets(installer.Markets) ?? AddMarkets(manifest.Markets); + newInstaller.NestedInstallerType = installer.NestedInstallerType ?? manifest.NestedInstallerType; + newInstaller.NestedInstallerFiles = AddNestedInstallerFiles(installer.NestedInstallerFiles) ?? AddNestedInstallerFiles(manifest.NestedInstallerFiles); + newInstaller.DisplayInstallWarnings = installer.DisplayInstallWarnings ?? manifest.DisplayInstallWarnings; + newInstaller.UnsupportedArguments = installer.UnsupportedArguments.ToApiArray() ?? manifest.UnsupportedArguments.ToApiArray(); + newInstaller.InstallationMetadata = AddInstallationMetadata(installer.InstallationMetadata) ?? AddInstallationMetadata(manifest.InstallationMetadata); + newInstaller.DownloadCommandProhibited = installer.DownloadCommandProhibited ?? manifest.DownloadCommandProhibited; newInstaller.RepairBehavior = installer.RepairBehavior ?? manifest.RepairBehavior; - newInstaller.ArchiveBinariesDependOnPath = installer.ArchiveBinariesDependOnPath ?? manifest.ArchiveBinariesDependOnPath ?? false; + newInstaller.ArchiveBinariesDependOnPath = installer.ArchiveBinariesDependOnPath ?? manifest.ArchiveBinariesDependOnPath; newInstaller.InstallerIdentifier = string.Join("_", newInstaller.Architecture, newInstaller.InstallerLocale, newInstaller.Scope, Guid.NewGuid()); versionExtended.AddInstaller(newInstaller); @@ -147,9 +176,73 @@ public static PackageManifest AddManifestToPackageManifest( return packageManifest; } - /// - /// Process Installer Switches subnode. - /// + private static Utils.Models.Arrays.Agreements AddAgreements(List sourceAgreements) + { + if (sourceAgreements != null) + { + var agreements = new Utils.Models.Arrays.Agreements(); + foreach (var sourceAgreement in sourceAgreements) + { + var agreement = new Utils.Models.Objects.SourceAgreement + { + AgreementLabel = sourceAgreement.AgreementLabel, + Agreement = sourceAgreement.Agreement, + AgreementUrl = sourceAgreement.AgreementUrl, + }; + agreements.Add(agreement); + } + + return agreements; + } + + return null; + } + + private static Utils.Models.Arrays.Documentations AddDocumentations(List sourceDocumentations) + { + if (sourceDocumentations != null) + { + var documentations = new Utils.Models.Arrays.Documentations(); + foreach (var sourceDocumentation in sourceDocumentations) + { + var documentation = new Utils.Models.Objects.Documentation + { + DocumentLabel = sourceDocumentation.DocumentLabel, + DocumentUrl = sourceDocumentation.DocumentUrl, + }; + documentations.Add(documentation); + } + + return documentations; + } + + return null; + } + + private static Utils.Models.Arrays.Icons AddIcons(List sourceIcons) + { + if (sourceIcons != null) + { + var icons = new Utils.Models.Arrays.Icons(); + foreach (var sourceIcon in sourceIcons) + { + var icon = new Utils.Models.Objects.Icon + { + IconUrl = sourceIcon.IconUrl, + IconFileType = sourceIcon.IconFileType, + IconResolution = sourceIcon.IconResolution, + IconTheme = sourceIcon.IconTheme, + IconSha256 = sourceIcon.IconSha256, + }; + icons.Add(icon); + } + + return icons; + } + + return null; + } + private static Utils.Models.Objects.InstallerSwitches AddInstallerSwitches(InstallerSwitches sourceSwitches) { if (sourceSwitches != null) @@ -170,9 +263,41 @@ private static Utils.Models.Objects.InstallerSwitches AddInstallerSwitches(Insta return null; } - /// - /// Process dependencies subnode. - /// + private static Utils.Models.Arrays.ExpectedReturnCodes AddExpectedReturnCodes(List sourceExpectedReturnCodes) + { + if (sourceExpectedReturnCodes != null) + { + var expectedReturnCodes = new Utils.Models.Arrays.ExpectedReturnCodes(); + foreach (var sourceExpectedReturnCode in sourceExpectedReturnCodes) + { + var expectedReturnCode = new Utils.Models.Objects.ExpectedReturnCode + { + InstallerReturnCode = sourceExpectedReturnCode.InstallerReturnCode, + ReturnResponse = sourceExpectedReturnCode.ReturnResponse, + + // Todo: Needs WinGetUtilInterop update + // ReturnResponseUrl = sourceExpectedReturnCode.ReturnResponseUrl, + }; + expectedReturnCodes.Add(expectedReturnCode); + } + + return expectedReturnCodes; + } + + return null; + } + + private static DateTime? TryParseDateTime(string dateTimeString) + { + DateTime result; + if (DateTime.TryParse(dateTimeString, out result)) + { + return result; + } + + return null; + } + private static Utils.Models.Objects.Dependencies AddDependencies(InstallerDependency sourceDependencies) { if (sourceDependencies != null) @@ -204,6 +329,98 @@ private static Utils.Models.Objects.Dependencies AddDependencies(InstallerDepend return null; } + private static Utils.Models.Arrays.AppsAndFeaturesEntries AddAppsAndFeaturesEntries(List sourceEntries) + { + if (sourceEntries != null) + { + var entries = new Utils.Models.Arrays.AppsAndFeaturesEntries(); + foreach (var sourceEntry in sourceEntries) + { + var entry = new Utils.Models.Objects.AppsAndFeatures + { + DisplayName = sourceEntry.DisplayName, + Publisher = sourceEntry.Publisher, + DisplayVersion = sourceEntry.DisplayVersion, + ProductCode = sourceEntry.ProductCode, + UpgradeCode = sourceEntry.UpgradeCode, + InstallerType = sourceEntry.InstallerType, + }; + entries.Add(entry); + } + + return entries; + } + + return null; + } + + private static Utils.Models.Objects.Markets AddMarkets(InstallerMarkets sourceMarkets) + { + if (sourceMarkets != null) + { + var markets = new Utils.Models.Objects.Markets(); + + markets.AllowedMarkets = sourceMarkets.AllowedMarkets.ToApiArray(); + markets.ExcludedMarkets = sourceMarkets.ExcludedMarkets.ToApiArray(); + + return markets; + } + + return null; + } + + private static Utils.Models.Arrays.NestedInstallerFiles AddNestedInstallerFiles(List sourceNestedInstallerFiles) + { + if (sourceNestedInstallerFiles != null) + { + var nestedInstallerFiles = new Utils.Models.Arrays.NestedInstallerFiles(); + foreach (var sourceNestedInstallerFile in sourceNestedInstallerFiles) + { + var nestedInstallerFile = new Utils.Models.Objects.NestedInstallerFile + { + RelativeFilePath = sourceNestedInstallerFile.RelativeFilePath, + PortableCommandAlias = sourceNestedInstallerFile.PortableCommandAlias, + }; + nestedInstallerFiles.Add(nestedInstallerFile); + } + + return nestedInstallerFiles; + } + + return null; + } + + private static Utils.Models.Objects.InstallationMetadata AddInstallationMetadata(InstallerInstallationMetadata sourceMetaData) + { + if (sourceMetaData != null) + { + var metadata = new Utils.Models.Objects.InstallationMetadata(); + + metadata.DefaultInstallLocation = sourceMetaData.DefaultInstallLocation; + + if (sourceMetaData.Files != null) + { + metadata.InstallationMetadataFiles = new Utils.Models.Arrays.InstallationMetadataFiles(); + foreach (var sourceFile in sourceMetaData.Files) + { + var file = new Utils.Models.Objects.InstallationMetadataFile + { + RelativeFilePath = sourceFile.RelativeFilePath, + FileSha256 = sourceFile.FileSha256, + FileType = sourceFile.FileType, + InvocationParameter = sourceFile.InvocationParameter, + DisplayName = sourceFile.DisplayName, + }; + metadata.InstallationMetadataFiles.Add(file); + } + } + + return metadata; + } + + return null; + } + private static T ToApiArray(this IEnumerable from) where T : ApiArray, new() { diff --git a/src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotes.cs b/src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotesValidator.cs similarity index 69% rename from src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotes.cs rename to src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotesValidator.cs index e9b0bfa2..17608cf9 100644 --- a/src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotes.cs +++ b/src/WinGet.RestSource.Utils/Validators/StringValidators/InstallationNotesValidator.cs @@ -1,5 +1,5 @@ -// ----------------------------------------------------------------------- -// +// ----------------------------------------------------------------------- +// // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // // ----------------------------------------------------------------------- @@ -9,16 +9,16 @@ namespace Microsoft.WinGet.RestSource.Utils.Validators.StringValidators /// /// ReleaseNotesValidator. /// - public class InstallationNotes : ApiStringValidator + public class InstallationNotesValidator : ApiStringValidator { private const bool Nullable = true; private const uint Max = 256; private const uint Min = 1; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public InstallationNotes() + public InstallationNotesValidator() { this.AllowNull = Nullable; this.MaxLength = Max; From 40e3504ab6615f24792350cae47cb584fa7b311e Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:20:38 -0800 Subject: [PATCH 11/16] fix apim template --- .../src/Library/New-ARMObjects.ps1 | 6 +- ...Object.ps1 => New-ARMParameterObjects.ps1} | 16 +- .../src/Library/New-MicrosoftEntraIdApp.ps1 | 110 +++++++++++ .../src/Library/New-WinGetSource.ps1 | 181 +++++++++++------- ...-ARMTemplate.ps1 => Test-ARMTemplates.ps1} | 9 +- .../ApiManagement/apimanagement.json | 16 +- 6 files changed, 251 insertions(+), 87 deletions(-) rename Tools/PowershellModule/src/Library/{New-ARMParameterObject.ps1 => New-ARMParameterObjects.ps1} (96%) create mode 100644 Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 rename Tools/PowershellModule/src/Library/{Test-ARMTemplate.ps1 => Test-ARMTemplates.ps1} (88%) diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 8704c25c..6fde163e 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -7,11 +7,11 @@ Function New-ARMObjects Creates the Azure Resources to stand-up a Windows Package Manager REST Source. .DESCRIPTION - Uses the custom PowerShell object provided by the "New-ARMParameterObject" cmdlet to create Azure resources, and will + Uses the custom PowerShell object provided by the "New-ARMParameterObjects" cmdlet to create Azure resources, and will create the the Key Vault secrets and publish the Windows Package Manager REST source REST apis to the Azure Function. .PARAMETER ARMObjects - Object returned from the "New-ARMParameterObject" providing the paths to the ARM Parameters and Template files. + Object returned from the "New-ARMParameterObjects" providing the paths to the ARM Parameters and Template files. .PARAMETER RestSourcePath Path to the compiled Function ZIP containing the REST APIs @@ -56,7 +56,7 @@ Function New-ARMObjects ## Creates the Azure Resources following the ARM template / parameters Write-Information "Creating Azure Resources following ARM Templates." - ## This is order specific, please ensure you used the New-ARMParameterObject function to create this object in the pre-determined order. + ## This is order specific, please ensure you used the New-ARMParameterObjects function to create this object in the pre-determined order. foreach ($Object in $ARMObjects) { Write-Information " Creating the Azure Object - $($Object.ObjectType)" diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 similarity index 96% rename from Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 rename to Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 index 8550f3a6..9051a72f 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObject.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Function New-ARMParameterObject +Function New-ARMParameterObjects { <# .SYNOPSIS @@ -27,7 +27,7 @@ Function New-ARMParameterObject ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. .EXAMPLE - New-ARMParameterObject -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Developer" + New-ARMParameterObjects -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Developer" Creates the Parameter files required for the creation of the ARM objects. @@ -38,9 +38,17 @@ Function New-ARMParameterObject [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance + [Parameter(Position=5, Mandatory=$true)] [string]$PublisherName, + [Parameter(Position=6, Mandatory=$true)] [string]$PublisherEmail, + [ValidateSet("None", "MicrosoftEntraId")] + [Parameter(Position=7, Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter(Position=8, Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", ) BEGIN { + $ARMObjects = @() + ## The Names that are to be assigned to each resource. $AppInsightsName = "appin-" + $Name -replace "[^a-zA-Z0-9-]", "" $KeyVaultName = "kv-" + $Name -replace "[^a-zA-Z0-9-]", "" @@ -52,8 +60,8 @@ Function New-ARMParameterObject $apiManagementName = "apim-" + $Name -replace "[^a-zA-Z0-9-]", "" ## Not supported in deployment script - ## $FrontDoorName = "" - ## $aspGenevaName = "" + ## $FrontDoorName = "" + ## $aspGenevaName = "" ## The names of the Azure Cosmos Database and Container - Do not change (Must match with the values in the compiled ## Windows Package Manager Functions [WinGet.RestSource.Functions.zip]) diff --git a/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 b/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 new file mode 100644 index 00000000..2ce6956f --- /dev/null +++ b/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 @@ -0,0 +1,110 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Function New-MicrosoftEntraIdApp +{ + <# + .SYNOPSIS + Creates a new Microsoft Entra Id app registration to be used for WinGet rest source Microsoft Entra Id based authentication. + + .DESCRIPTION + Creates a new Microsoft Entra Id app registration to be used for WinGet rest source Microsoft Entra Id based authentication. + Always create a new Microsoft Entra Id app registration. + + .PARAMETER Name + Name of the Microsoft Entra Id app to be created. + + .EXAMPLE + New-MicrosoftEntraIdApp -Name "contosoapp" + + Assumes an active connection to Azure. Creates a new Microsoft Entra Id app named "contosoapp". + #> + + PARAM( + [Parameter(Position=0, Mandatory=$true)] [string]$Name + ) + + $Return = @{ + Result = $false + Resource = "" + ResourceScope = "" + } + + ## Normalize Microsoft Entra Id app name + $NormalizedName = $Name -replace "[^a-zA-Z0-9-()_.]", "" + if($Name -cne $NormalizedName) { + $Name = $NormalizedName + Write-Warning "Removed special characters from the Microsoft Entra Id app name (New Name: $Name)." + } + + ## Creating a line break from previous steps + Write-Information "Microsoft Entra Id app name to be created: $Name" + + $App = New-AzADApplication -DisplayName $Name -AvailableToOtherTenants $false + if (!$App) + { + Write-Error "Failed to create Microsoft Entra Id app. Name: $Name" + return $Return + } + + ## Add App Id Uri + $AppId = $App.AppId + Update-AzADApplication -ApplicationId $AppId -IdentifierUri "api://$AppId" -ErrorVariable ErrorUpdate + if ($ErrorUpdate) + { + Write-Error "Failed to add App Id Uri" + return $Return + } + + ## Add Api scope + $ScopeId = [guid]::NewGuid().ToString() + $ScopeName = "user_impersonation" + $Api = @{ + Oauth2PermissionScopes = @( + @{ + AdminConsentDescription = "Sign in to access $Name WinGet rest source" + AdminConsentDisplayName = "Access WinGet rest source" + UserConsentDescription = "Sign in to access $Name WinGet rest source" + UserConsentDisplayName = "Access WinGet rest source" + Id = $ScopeId + IsEnabled = $true + Type = "User" + Value = $ScopeName + } + ) + } + Update-AzADApplication -ApplicationId $AppId -Api $Api -ErrorVariable ErrorUpdate + if ($ErrorUpdate) + { + Write-Error "Failed to add Api scope" + return $Return + } + + ## Add authorized client + $Api = @{ + PreAuthorizedApplications = @( + @{ + AppId = "7b8ea11a-7f45-4b3a-ab51-794d5863af15" + DelegatedPermissionIds = @($ScopeId) + } + @{ + AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" + DelegatedPermissionIds = @($ScopeId) + } + @{ + AppId = "1950a258-227b-4e31-a9cf-717495945fc2" + DelegatedPermissionIds = @($ScopeId) + } + ) + } + Update-AzADApplication -ApplicationId $AppId -Api $Api -ErrorVariable ErrorUpdate + if ($ErrorUpdate) + { + Write-Error "Failed to add authorized clients" + return $Return + } + + $Return.Result = $true + $Return.Resource = $AppId + $Return.ResourceScope = $ScopeName + return $Return +} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index cfbf2f92..a6b6ba2f 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -66,90 +66,127 @@ Function New-WinGetSource [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplate", [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", + [Parameter(Position=7, Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Position=8, Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("Developer", "Basic", "Enhanced")] - [Parameter(Position=7, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [Parameter(Position=9, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [ValidateSet("None", "MicrosoftEntraId")] + [Parameter(Position=10,Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter()] [switch]$CreateNewMicrosoftEntraIdAppRegistration, + [Parameter(Position=11,Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Position=12,Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", [Parameter()] [switch]$ShowConnectionInstructions ) - BEGIN - { - if($ImplementationPerformance -eq "Developer") { - Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" - } + + if($ImplementationPerformance -eq "Developer") { + Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" + } + + ############################### + ## Check input paths + if(!$(Test-Path -Path $TemplateFolderPath)) { + Write-Error "REST Source Function Code is missing in specified path ($TemplateFolderPath)" + return $false + } + if(!$(Test-Path -Path $RestSourcePath)) { + Write-Error "REST Source Function Code is missing in specified path ($RestSourcePath)" + return $false + } + + ############################### + ## Check Microsoft Entra Id input + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !CreateNewMicrosoftEntraIdAppRegistration -and !MicrosoftEntraIdResource) { + Write-Error "When Microsoft Entra Id authentication is requested, either CreateNewMicrosoftEntraIdAppRegistration should be requested or MicrosoftEntraIdResource should be provided." + return $false + } + + ############################### + ## Create folder for the Parameter output path + $Result = New-Item -ItemType Directory -Path $ParameterOutputPath -Force + if($Result) { + Write-Verbose -Message "Created Directory to contain the ARM Parameter files ($($Result.FullName))." + } + else { + Write-Error "Failed to create ARM parameter files output path. Path: $ParameterOutputPath" + return $false + } + + ############################### + ## Connects to Azure, if not already connected. + Write-Information "Testing connection to Azure." + $Result = Connect-ToAzure -SubscriptionName $SubscriptionName + if(!($Result)) { + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + return $false } - PROCESS - { - ############################### - ## Check input paths - if(!$(Test-Path -Path $TemplateFolderPath)) { - throw "REST Source Function Code is missing in specified path ($TemplateFolderPath)" - } - if(!$(Test-Path -Path $RestSourcePath)) { - throw "REST Source Function Code is missing in specified path ($RestSourcePath)" - } - ############################### - ## Create folder for the Parameter output path - $Result = New-Item -ItemType Directory -Path $ParameterOutputPath -Force - if($Result) { - Write-Verbose -Message "Created Directory to contain the ARM Parameter files ($($Result.FullName))." + ############################### + ## Create new Microsoft Entra Id app registration if requested + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and $CreateNewMicrosoftEntraIdAppRegistration) { + $Result = New-MicrosoftEntraIdApp -Name $Name + if (!$Result.Result) { + Write-Error "Failed to create new Microsoft Entra Id app registration." + return $false } else { - throw "Failed to create ARM parameters output path. Path: $ParameterOutputPath" + $MicrosoftEntraIdResource = !$Result.Resource + $MicrosoftEntraIdResourceScope = !$Result.ResourceScope } + } - ############################### - ## Connects to Azure, if not already connected. - Write-Information "Testing connection to Azure." - $Result = Connect-ToAzure -SubscriptionName $SubscriptionName - if(!($Result)) { - throw "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." - } + ############################### + ## Creates the ARM files + $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance + if (!$ARMObjects) { + Write-Error "Failed to create ARM parameter objects." + return $false + } - ############################### - ## Creates the ARM files - $ARMObjects = New-ARMParameterObject -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance - - ############################### - ## Create Resource Group - Write-Information "Creating the Resource Group used to host the Windows Package Manager REST source. Name: $ResourceGroup, Region: $Region" - Add-AzureResourceGroup -Name $ResourceGroup -Region $Region - - #### Verifies ARM Parameters are correct - $Result = Test-ARMTemplate -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup - if($Result){ - $ErrReturnObject = @{ - ARMObjects = $ARMObjects - ResourceGroup = $ResourceGroup - Result = $Result - } - - Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject - } + ############################### + ## Create Resource Group + Write-Information "Creating the Resource Group used to host the Windows Package Manager REST source. Name: $ResourceGroup, Region: $Region" + $Result = Add-AzureResourceGroup -Name $ResourceGroup -Region $Region + if (!$Result) { + Write-Error "Failed to create Azure resource group. Name: $ResourceGroup Region: $Region" + return $false + } - ############################### - ## Creates Azure Objects with ARM Templates and Parameters - New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup - - ############################### - ## Shows how to connect local Windows Package Manager Client to newly created REST source - if($ShowConnectionInstructions) { - $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) | ConvertFrom-Json - $AzFunctionName = $jsonFunction.Parameters.FunctionName.Value - $AzFunctionURL = $(Get-AzFunctionApp -Name $AzFunctionName -ResourceGroupName $ResourceGroup).DefaultHostName - - ## Post script Run Informational: - #### Instructions on how to add the REST source to your Windows Package Manager Client - Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" - Write-Information -MessageData " winget source add -n ""restsource"" -a ""https://$AzFunctionURL/api/"" -t ""Microsoft.Rest""" - Write-Verbose -Message "Use the following command to register the new REST source with your Windows Package Manager Client:" - Write-Verbose -Message " winget source add -n ""restsource"" -a ""https://$AzFunctionURL/api/"" -t ""Microsoft.Rest""" - - #### For more information about how to use the solution, visit the aka.ms link. - Write-Information -MessageData "`n For more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" + #### Verifies ARM Parameters are correct + $Result = Test-ARMTemplates -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup + if($Result){ + $ErrReturnObject = @{ + ARMObjects = $ARMObjects + ResourceGroup = $ResourceGroup + Result = $Result } + + Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject + return $false } - END - { - return $true + + ############################### + ## Creates Azure Objects with ARM Templates and Parameters + $Result = New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup + if (!$Result) { + Write-Error "Failed to create Azure resources for WinGet rest source" + return $false + } + + ############################### + ## Shows how to connect local Windows Package Manager Client to newly created REST source + if($ShowConnectionInstructions) { + $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) | ConvertFrom-Json + $AzFunctionName = $jsonFunction.Parameters.FunctionName.Value + $AzFunctionURL = $(Get-AzFunctionApp -Name $AzFunctionName -ResourceGroupName $ResourceGroup).DefaultHostName + + ## Post script Run Informational: + #### Instructions on how to add the REST source to your Windows Package Manager Client + Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" -InformationAction Continue + Write-Information -MessageData " winget source add -n ""restsource"" -a ""https://$AzFunctionURL/api/"" -t ""Microsoft.Rest""" -InformationAction Continue + + #### For more information about how to use the solution, visit the aka.ms link. + Write-Information -MessageData "`nFor more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" -InformationAction Continue } + + return $true } diff --git a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 b/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 similarity index 88% rename from Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 rename to Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 index 97e649b8..8a2cef2b 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMTemplate.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 @@ -1,11 +1,12 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Function Test-ARMTemplate +Function Test-ARMTemplates { <# .SYNOPSIS Validates that the parameter files have been build correctly, matches to the template files, and can be used to build Azure - resources. Will also validate that the naming used for the resources is available, and meets requirements. Returns boolean. + resources. Will also validate that the naming used for the resources is available, and meets requirements. Returns a list of + failed validations. If all validations pass, returns empty. .DESCRIPTION Validates that the parameter files have been build correctly, matches to the template files, and can be used to build Azure @@ -13,13 +14,13 @@ Function Test-ARMTemplate failed validations. If all validations pass, returns empty. .PARAMETER ARMObjects - Object Returned from the New-ARMParameterObject. + Object Returned from the New-ARMParameterObjects. .PARAMETER ResourceGroup The Resource Group that the objects will be tested in reference to. .EXAMPLE - Test-ARMTemplate -ARMObjects $ARMObjects -ResourceGroup "WinGet" + Test-ARMTemplates -ARMObjects $ARMObjects -ResourceGroup "WinGet" Tests that the Azure Resource can be created in the specified Azure Resource Group with the parameter and template files. diff --git a/src/WinGet.RestSource.Infrastructure/Templates/ApiManagement/apimanagement.json b/src/WinGet.RestSource.Infrastructure/Templates/ApiManagement/apimanagement.json index f412c1b7..149ef07f 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/ApiManagement/apimanagement.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/ApiManagement/apimanagement.json @@ -102,14 +102,22 @@ "metadata": { "description": "Api validation policy for source management apis. If this parameter is empty, a default validate-azure-ad-token policy is used." } + }, + "microsoftEntraIdResource": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. If provided, the value will be used as the token audience in the default validate-azure-ad-token policy." + } } }, "variables": { "kv_secreturi_path": "[concat('https://', parameters('keyVaultName'), '.vault.azure.net/secrets/')]", "kv_functionHostKey_SecretName": "AzureFunctionHostKey", "backendPoolName": "[concat(parameters('apiName'), '-backend-pool')]", - "queryApiValidation": "[if(parameters('queryApiValidationEnabled'), if(empty(parameters('queryApiValidationPolicy')), concat('7b8ea11a-7f45-4b3a-ab51-794d5863af1504b07795-8ddb-461a-bbee-02f9e1bf7b461950a258-227b-4e31-a9cf-717495945fc2'), parameters('queryApiValidationPolicy')), '')]", - "manageApiValidation": "[if(empty(parameters('manageApiValidationPolicy')), concat('04b07795-8ddb-461a-bbee-02f9e1bf7b461950a258-227b-4e31-a9cf-717495945fc29b895d92-2cd3-44c7-9d02-a6ac2d5ea5c362e90394-69f5-4237-9190-012177145e10'), parameters('manageApiValidationPolicy'))]", + "entraIdTokenAudience": "[concat('', if(empty(parameters('microsoftEntraIdResource')), 'https://management.core.windows.net/https://management.azure.com/', concat('', parameters('microsoftEntraIdResource'), '')), '')]", + "queryApiValidation": "[if(parameters('queryApiValidationEnabled'), if(empty(parameters('queryApiValidationPolicy')), concat('7b8ea11a-7f45-4b3a-ab51-794d5863af1504b07795-8ddb-461a-bbee-02f9e1bf7b461950a258-227b-4e31-a9cf-717495945fc2', variables('entraIdTokenAudience'), ''), parameters('queryApiValidationPolicy')), '')]", + "manageApiValidation": "[if(empty(parameters('manageApiValidationPolicy')), concat('04b07795-8ddb-461a-bbee-02f9e1bf7b461950a258-227b-4e31-a9cf-717495945fc2', variables('entraIdTokenAudience'), '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c362e90394-69f5-4237-9190-012177145e10'), parameters('manageApiValidationPolicy'))]", "publicApiPolicy": { "value": "[concat('')]", "format": "xml" @@ -130,8 +138,8 @@ "name": "[parameters('serviceName')]", "location": "[parameters('location')]", "sku": { - "name": "Developer", - "capacity": 1 + "name": "[parameters('sku')]", + "capacity": "[parameters('skuCount')]" }, "identity": { "type": "SystemAssigned" From 9e73e0996975d83c54accfa49ea9668be5486a73 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:17:15 -0800 Subject: [PATCH 12/16] rewritten and done --- .../src/Library/New-ARMObjects.ps1 | 241 +++--- .../src/Library/New-ARMParameterObjects.ps1 | 807 +++++++++--------- .../src/Library/New-WinGetSource.ps1 | 25 +- .../Templates/AppConfig/appconfig.json | 9 +- .../CosmosDB/cosmosdb-sql-container.json | 4 +- .../Templates/CosmosDB/cosmosdb.json | 6 +- 6 files changed, 561 insertions(+), 531 deletions(-) diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 6fde163e..c0060137 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -29,131 +29,152 @@ Function New-ARMObjects [Parameter(Position=1, Mandatory=$true)] [string] $RestSourcePath, [Parameter(Position=2, Mandatory=$true)] [string] $ResourceGroup ) - BEGIN - { - ## Imports the contents of the Parameter Files for reference and logging purposes: - $jsonStorageAccount = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "StorageAccount" }).ParameterPath) | ConvertFrom-Json - $jsoncdba = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "CosmosDBAccount"}).ParameterPath) | ConvertFrom-Json - $jsonKeyVault = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Keyvault"}).ParameterPath) | ConvertFrom-Json - $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) | ConvertFrom-Json - - ## Azure resource names retrieved from the Parameter files. - $AzKeyVaultName = $jsonKeyVault.parameters.name.value - $AzStorageAccountName = $jsonStorageAccount.parameters.storageAccountName.value - $CosmosAccountName = $jsoncdba.Parameters.Name.Value - - ## Azure Keyvault Secret Names - Do not change values (Must match with values in the Template files) - $AzStorageAccountKeyName = "AzStorageAccountKey" - $CosmosAccountEndpointKeyName = "CosmosAccountEndpoint" - $CosmosAccountKeyWriteKeyName = "CosmosReadWriteKey" - $CosmosAccountKeyReadKeyName = "CosmosReadOnlyKey" - - ## Azure Storage Account Connection String Endpoint Suffix - $AzEndpointSuffix = "core.windows.net" - } - PROCESS + + # Function to Create Random Strings + function Create-AppKey() { - ## Creates the Azure Resources following the ARM template / parameters - Write-Information "Creating Azure Resources following ARM Templates." - - ## This is order specific, please ensure you used the New-ARMParameterObjects function to create this object in the pre-determined order. - foreach ($Object in $ARMObjects) { - Write-Information " Creating the Azure Object - $($Object.ObjectType)" + $private:characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ' + $private:randomChars = 1..64 | ForEach-Object { Get-Random -Maximum $characters.length } - ## If the object to be created is an Azure Function, then complete these pre-required steps before creating the Azure Function. - if($Object.ObjectType -eq "Function") { - Write-Verbose " Creating KeyVault Secrets:" + # Set the output field separator to empty instead of space + $private:ofs="" + return [String]$characters[$randomChars] + } - ## Creates a reference to the Azure Storage Account Connection String as a Secret in the Azure Keyvault. - $AzStorageAccountKey = $(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -Name $AzStorageAccountName)[0].Valu + ## Azure resource names retrieved from the Parameter files. + $StorageAccountName = $ARMObjects.Where({$_.ObjectType -eq "StorageAccount"}).Parameters.Parameters.storageAccountName.value + $KeyVaultName = $ARMObjects.Where({$_.ObjectType -eq "Keyvault"}).Parameters.Parameters.name.value + $CosmosAccountName = $ARMObjects.Where({$_.ObjectType -eq "CosmosDBAccount"}).Parameters.Parameters.name.value + $AppConfigName = $ARMObjects.Where({$_.ObjectType -eq "AppConfig"}).Parameters.Parameters.appConfigName.value - ## Adds the Azure Storage Account Connection String to the Keyvault - Write-Verbose -Message " Creating Keyvault Secret for Azure Storage Account Connection String." - Write-Information -MessageData " Creating Keyvault Secret for Azure Storage Account Connection String." - $Result = Set-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $AzStorageAccountKeyName -SecretValue $AzStorageAccountConnectionString + ## Azure Keyvault Secret Names - Do not change values (Must match with values in the Template files) + $CosmosAccountEndpointKeyName = "CosmosAccountEndpoint" + $AzureFunctionHostKeyName = "AzureFunctionHostKey" + $AppConfigPrimaryEndpointName = "AppConfigPrimaryEndpoint" + $AppConfigSecondaryEndpointName = "AppConfigSecondaryEndpoint" - ## Adds the Azure Cosmos Account Endpoint URL to the Keyvault - Write-Verbose -Message " Creating Keyvault Secret for Azure CosmosDB Connection String." - Write-Information -MessageData " Creating Keyvault Secret for Azure CosmosDB Connection String." - $Result = Set-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $CosmosAccountEndpointKeyName -SecretValue $CosmosAccountEndpointValue + $FunctionAppUrls = @() - ## Adds the Azure Cosmos Primary Account Key to the Keyvault - Write-Verbose -Message " Creating Keyvault Secret for Azure CosmosDB Write Primary Key." - Write-Information -MessageData " Creating Keyvault Secret for Azure CosmosDB Write Primary Key." - $Result = Set-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $CosmosAccountKeyWriteKeyName -SecretValue $CosmosAccountKeyWriteValue - - ## Adds the Azure Cosmos Primary Account Key to the Keyvault - Write-Verbose -Message " Creating Keyvault Secret for Azure CosmosDB Read Primary Key." - Write-Information -MessageData " Creating Keyvault Secret for Azure CosmosDB Read Primary Key." - $Result = Set-AzKeyVaultSecret -VaultName $AzKeyVaultName -Name $CosmosAccountKeyReadKeyName -SecretValue $CosmosAccountKeyReadValue - - ## Create base object of the Azure Function, generating reference object ID for Keyvault - Write-Verbose -Message " Creating base Azure Function object." - Write-Information -MessageData " Creating base Azure Function object." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental - } + ## Creates the Azure Resources following the ARM template / parameters + Write-Information "Creating Azure Resources following ARM Templates." - ## Creates the Azure Resource - Write-Verbose -Message " Creating $($Object.ObjectType) following the ARM Parameter File..." - Write-Information -MessageData " Creating $($Object.ObjectType) following the ARM Parameter File..." - $Result = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable objerror -AsJob + ## This is order specific, please ensure you used the New-ARMParameterObjects function to create this object in the pre-determined order. + foreach ($Object in $ARMObjects) { + Write-Information " Creating the Azure Object - $($Object.ObjectType)" - while ($Result.State -eq "Running") { - ## Sets a sleep of 10 seconds after object creation to allow Azure to update creation status, and mark as "running" - Start-Sleep -Seconds 10 - } + ## If the object to be created is an Azure Function, then complete these pre-required steps before creating the Azure Function. + if ($Object.ObjectType -eq "Function") { + Write-Verbose " Creating KeyVault Secrets:" - ## Sets an additional sleep of 10 seconds, to account for delays in availability - Start-Sleep -Seconds 10 - - ## Verifies that no error occured when creating the Azure resource - if($objerror -or $Result.Error) { - $ErrReturnObject = @{ - ObjectError = $objerror - JobError = $Result.Error + $CosmosAccountEndpointValue = $(Get-AzCosmosDBAccount -Name $CosmosAccountName -ResourceGroupName $ResourceGroup).DocumentEndpoint | ConvertTo-SecureString -AsPlainText -Force + Write-Verbose " Creating Keyvault Secret for Azure CosmosDB endpoint." + Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $CosmosAccountEndpointKeyName -SecretValue $CosmosAccountEndpointValue + + $AppConfigEndpointValue = $(Get-AzAppConfigurationStore -Name azpstest-appstore -ResourceGroupName $ResourceGroup).Endpoint | ConvertTo-SecureString -AsPlainText -Force + Write-Verbose " Creating Keyvault Secret for Azure App Config endpoint." + Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AppConfigPrimaryEndpointName -SecretValue $AppConfigEndpointValue + Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AppConfigSecondaryEndpointName -SecretValue $AppConfigEndpointValue + } + elseif ($Object.ObjectType -eq "ApiManagement") { + ## Create instance manually if not exist + $ApiManagementParameters = $Object.Parameters.Parameters + $ApiManagement = Get-AzApiManagement -ResourceGroupName $ResourceGroup -Name $ApiManagementParameters.serviceName.value + if (!$ApiManagement) { + Write-Information "Creating new Api Aanagement service. Name: $($ApiManagementParameters.serviceName.value)" + $ApiManagement = New-AzApiManagement -ResourceGroupName $ResourceGroup -Name $ApiManagementParameters.serviceName.value -Location $ApiManagementParameters.location.value -Organization $ApiManagementParameters.publisherName.value -AdminEmail $ApiManagementParameters.publisherEmail.value -Sku $ApiManagementParameters.sku.value -SystemAssignedIdentity -ErrorVariable DeployError + if ($DeployError) { + Write-Error "Failed to create Api Aanagement service. $DeployError" + return $false } - ## Creating the object following the ARM template failed. - ## TODO: extend error reporting in logs across scripts. - Write-Error "Failed to create Azure object. $($Result.Error)" -TargetObject $ErrReturnObject - return - } - else { - ## Creating the object was successful - Write-Verbose -Message " $($Object.ObjectType) was successfully created." - Write-Information -MessageData " $($Object.ObjectType) was successfully created." } + + ## Update backend urls and re-create parameters file + $ApiManagementParameters.backendUrls.value = $FunctionAppUrls + Write-Verbose -Message " Re-creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" + $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 + $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + + ## Set secret get for Api management service + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $ApiManagement.Identity.PrincipalId -PermissionsToSecrets Get + } - ## Publish GitHub Functions to newly created Azure Function - if($Object.ObjectType -eq "Function") { - ## Gets the Azure Function Name from the Parameter JSON file contents. - $AzFunctionName = $jsonFunction.parameters.functionName.value + ## Creates the Azure Resource + $DeployJob = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable DeployError -AsJob - ## Verifies the presence of the "WinGet.RestSource.Functions.zip" file. - Write-Verbose -Message " Confirming Compiled Azure Functions is present" - if(Test-Path $RestSourcePath) { - Start-Sleep -Seconds 10 - - ## The "WinGet.RestSource.Functions.zip" was found in the working directory - Write-Verbose -Message " File Path Found: $RestSourcePath" - - ## Uploads the Windows Package Manager functions to the Azure Function. - Write-Verbose -Message " Copying function files to the Azure Function." - Write-Information -MessageData " Copying function files to the Azure Function." - $Result = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $AzFunctionName -Force - } - else { - $ErrReturnObject = @{ - FunctionArchivePath = $RestSourcePath - TestPathResults = Test-Path $RestSourcePath - } - ## The "WinGet.RestSource.Functions.zip" was not found. Unable to uploaded to the Azure Function. - Write-Error "File Path not found: $RestSourcePath" -TargetObject $ErrReturnObject + while ($DeployJob.State -eq "Running") { + ## Sets a sleep of 2 seconds after object creation to allow Azure to update creation status, and mark as "running" + Start-Sleep -Seconds 2 + } + + ## Verifies that no error occured when creating the Azure resource + if ($DeployError -or $DeployJob.State -ne "Completed") { + $ErrReturnObject = @{ + DeployError = $DeployError + JobError = $DeployJob + } + + ## TODO: extend error reporting in logs across scripts. + Write-Error "Failed to create Azure object. $DeployError" -TargetObject $ErrReturnObject + return $false + } + + Write-Information -MessageData " $($Object.ObjectType) was successfully created." + + ## Publish GitHub Functions to newly created Azure Function + if($Object.ObjectType -eq "Function") { + $FunctionName = $Object.Parameters.Parameters.functionName.value + $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroup -Name $FunctionName + $FunctionAppId = $FunctionApp.Id + + $FunctionAppUrls += "https://$($FunctionApp.DefaultHostName)/api" + + ## Create Function app key and also add to keyvault + $local:NewFunctionKeyValue = Create-AppKey + $Result = Invoke-AzRestMethod -Path "$FunctionAppId/host/default/functionKeys/WinGetRestSourceAccess?api-version=2024-04-01" -Method PUT -Payload (@{properties=@{value = $NewFunctionKeyValue}} | ConvertTo-Json -Depth 8) + if ($Result.StatusCode -ne 200 -and $Result.StatusCode -ne 201) { + Write-Error "Failed to create Azure Function key. $($Result.Content)" + return $false + } + + Write-Information -MessageData " Add Function App host key to keyvault." + Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AzureFunctionHostKeyName -SecretValue ($NewFunctionKeyValue | ConvertTo-SecureString -AsPlainText -Force) + + ## Assign necessary roles + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Account Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Blob Data Owner" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Table Data Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Message Processor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Message Sender" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "App Configuration Data Reader" -ResourceGroupName $ResourceGroup -ResourceName $AppConfigName -ResourceType "Microsoft.AppConfiguration/configurationStores" + + ## Assign cosmos db roles + $RoleId = [guid]::NewGuid().ToString() + $CosmosDBDataAction = @("Microsoft.DocumentDB/databaseAccounts/readMetadata", "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*", "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*") + New-AzCosmosDBSqlRoleDefinition -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -Type CustomRole -RoleName "ReadWriteAll" -AssignableScope "/" -DataAction $CosmosDBDataAction -Id $RoleId + New-AzCosmosDBSqlRoleAssignment -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -RoleDefinitionId $RoleId -Scope "/" -PrincipalId $FunctionApp.Identity.PrincipalId + + Write-Information -MessageData " Publishing function files to the Azure Function." + $DeployJob = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $FunctionName -Force -AsJob -ErrorVariable DeployError + while ($DeployJob.State -eq "Running") { + Start-Sleep -Seconds 2 + } + + ## Verifies that no error occured when publishing the Function App + if ($DeployError -or $DeployJob.State -ne "Completed") { + $ErrReturnObject = @{ + DeployError = $DeployError + JobError = $DeployJob } + + Write-Error "Failed to publishing the Function App. $DeployError" -TargetObject $ErrReturnObject + return $false } + + ## Restart the Function App + Restart-AzFunctionApp -Name $FunctionName -ResourceGroupName $ResourceGroup -Force } } - END - { - return - } + + return $true } diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 index 9051a72f..c62065fe 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 @@ -26,6 +26,21 @@ Function New-ARMParameterObjects .PARAMETER ImplementationPerformance ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. + .PARAMETER PublisherName + [Optional] The WinGet rest source publisher name + + .PARAMETER PublisherEmail + [Optional] The WinGet rest source publisher email + + .PARAMETER RestSourceAuthentication + [Optional] ["None", "MicrosoftEntraId"] The WinGet rest source authentication type. (Default: None) + + .PARAMETER MicrosoftEntraIdResource + [Optional] Microsoft Entra Id authentication resource + + .PARAMETER MicrosoftEntraIdResourceScope + [Optional] Microsoft Entra Id authentication resource scope + .EXAMPLE New-ARMParameterObjects -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Developer" @@ -38,472 +53,442 @@ Function New-ARMParameterObjects [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance - [Parameter(Position=5, Mandatory=$true)] [string]$PublisherName, - [Parameter(Position=6, Mandatory=$true)] [string]$PublisherEmail, + [Parameter(Position=5, Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Position=6, Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("None", "MicrosoftEntraId")] [Parameter(Position=7, Mandatory=$false)] [string]$RestSourceAuthentication = "None", [Parameter(Position=8, Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", ) - BEGIN - { - $ARMObjects = @() - - ## The Names that are to be assigned to each resource. - $AppInsightsName = "appin-" + $Name -replace "[^a-zA-Z0-9-]", "" - $KeyVaultName = "kv-" + $Name -replace "[^a-zA-Z0-9-]", "" - $StorageAccountName = "st" + $Name.ToLower() -replace "[^a-z0-9]", "" - $aspName = "asp-" + $Name -replace "[^a-zA-Z0-9-]", "" - $CDBAccountName = "cosmos-" + $Name.ToLower() -replace "[^a-z0-9-]", "" - $FunctionName = "azfun-" + $Name -replace "[^a-zA-Z0-9-]", "" - $appConfigName = "appconfig-" + $Name -replace "[^a-zA-Z0-9-]", "" - $apiManagementName = "apim-" + $Name -replace "[^a-zA-Z0-9-]", "" - - ## Not supported in deployment script - ## $FrontDoorName = "" - ## $aspGenevaName = "" - ## The names of the Azure Cosmos Database and Container - Do not change (Must match with the values in the compiled - ## Windows Package Manager Functions [WinGet.RestSource.Functions.zip]) - $CDBDatabaseName = "WinGet" - $CDBContainerName = "Manifests" + $ARMObjects = @() + + ## The Names that are to be assigned to each resource. + $AppInsightsName = "appin-" + $Name -replace "[^a-zA-Z0-9-]", "" + $KeyVaultName = "kv-" + $Name -replace "[^a-zA-Z0-9-]", "" + $StorageAccountName = "st" + $Name.ToLower() -replace "[^a-z0-9]", "" + $AspName = "asp-" + $Name -replace "[^a-zA-Z0-9-]", "" + $CDBAccountName = "cosmos-" + $Name.ToLower() -replace "[^a-z0-9-]", "" + $FunctionName = "azfun-" + $Name -replace "[^a-zA-Z0-9-]", "" + $AppConfigName = "appconfig-" + $Name -replace "[^a-zA-Z0-9-]", "" + $ApiManagementName = "apim-" + $Name -replace "[^a-zA-Z0-9-]", "" + $ServerIdentifier = "WinGetRestSource-" + $Name -replace "[^a-zA-Z0-9-]", "" + + ## Not supported in deployment script + ## $FrontDoorName = "" + ## $AspGenevaName = "" - ## The values required for Function ARM Template. But not supported in deployment script. - $manifestCacheEndpoint = "" - $monitoringTenant = "" - $monitoringRole = "" - $monitoringMetricsAccount = "" - $runFromPackageUrl = "" - - ## Relative Path from the Working Directory to the Azure ARM Template Files - $TemplateAppInsightsPath = "$TemplateFolderPath\applicationinsights.json" - $TemplateKeyVaultPath = "$TemplateFolderPath\keyvault.json" - $TemplateStorageAccountPath = "$TemplateFolderPath\storageaccount.json" - $TemplateASPPath = "$TemplateFolderPath\asp.json" - $TemplateCDBAccountPath = "$TemplateFolderPath\cosmosdb.json" - $TemplateCDBPath = "$TemplateFolderPath\cosmosdb-sql.json" - $TemplateCDBContainerPath = "$TemplateFolderPath\cosmosdb-sql-container.json" - $TemplateFunctionPath = "$TemplateFolderPath\azurefunction.json" - $TemplateFrontDoorPath = "$TemplateFolderPath\frontdoor.json" - $TemplateAppConfigPath = "$TemplateFolderPath\appconfig.json" - - $ParameterAppInsightsPath = "$ParameterFolderPath\applicationinsights.json" - $ParameterKeyVaultPath = "$ParameterFolderPath\keyvault.json" - $ParameterStorageAccountPath = "$ParameterFolderPath\storageaccount.json" - $ParameterASPPath = "$ParameterFolderPath\asp.json" - $ParameterCDBAccountPath = "$ParameterFolderPath\cosmosdb.json" - $ParameterCDBPath = "$ParameterFolderPath\cosmosdb-sql.json" - $ParameterCDBContainerPath = "$ParameterFolderPath\cosmosdb-sql-container.json" - $ParameterFunctionPath = "$ParameterFolderPath\azurefunction.json" - $ParameterFrontDoorPath = "$ParameterFolderPath\frontdoor.json" - $ParameterAppConfigPath = "$ParameterFolderPath\appconfig.json" - - Write-Verbose -Message "ARM Parameter Resource performance is based on the: $ImplementationPerformance." + ## The names of the Azure Cosmos Database and Container - Do not change (Must match with the values in the compiled + ## Windows Package Manager Functions [WinGet.RestSource.Functions.zip]) + $CDBDatabaseName = "WinGet" + $CDBContainerName = "Manifests" + + if ($RestSourceAuthentication -eq "MicrosoftEntraId") { + $ServerAuthenticationType = "microsoftEntraId" + $QueryApiValidationEnabled = $true + } + else { + $ServerAuthenticationType = "none" + $MicrosoftEntraIdResource = "" + $MicrosoftEntraIdResourceScope = "" + $QueryApiValidationEnabled = $false + } - switch ($ImplementationPerformance) { - "Developer" { - $KeyVaultSKU = "Standard" - $StorageAccountPerformance = "Standard_LRS" - $ASPSKU = "B1" - $CosmosDBAEnableFreeTier = $true - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" - $CosmosDBACapabilities = "[]" - } - "Basic" { - $KeyVaultSKU = "Standard" - $StorageAccountPerformance = "Standard_GRS" - $ASPSKU = "S1" - $CosmosDBAEnableFreeTier = $false - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" - $CosmosDBACapabilities = "[]" - } - "Enhanced" { - $KeyVaultSKU = "Standard" - $StorageAccountPerformance = "Standard_GZRS" - $ASPSKU = "P1V2" - $CosmosDBAEnableFreeTier = $false - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" - $CosmosDBACapabilities = "[]" - } - } - $PrimaryRegionName = $(Get-AzLocation).Where({$_.Location -eq $Region}).DisplayName - $SecondaryRegion = Get-PairedAzureRegion -Region $Region - $SecondaryRegionName = $(Get-AzLocation).Where({$_.Location -eq $SecondaryRegion}).DisplayName + ## The values required for Function ARM Template. But not supported in deployment script. + $ManifestCacheEndpoint = "" + $MonitoringTenant = "" + $MonitoringRole = "" + $MonitoringMetricsAccount = "" + $RunFromPackageUrl = "" - ## The name of the Secret that will be created in the Azure Keyvault - Do not change - $AzKVStorageSecretName = "AzStorageAccountKey" + ## Relative Path from the Working Directory to the Azure ARM Template Files + $TemplateAppInsightsPath = "$TemplateFolderPath\applicationinsights.json" + $TemplateKeyVaultPath = "$TemplateFolderPath\keyvault.json" + $TemplateStorageAccountPath = "$TemplateFolderPath\storageaccount.json" + $TemplateASPPath = "$TemplateFolderPath\asp.json" + $TemplateCDBAccountPath = "$TemplateFolderPath\cosmosdb.json" + $TemplateCDBPath = "$TemplateFolderPath\cosmosdb-sql.json" + $TemplateCDBContainerPath = "$TemplateFolderPath\cosmosdb-sql-container.json" + $TemplateFunctionPath = "$TemplateFolderPath\azurefunction.json" + $TemplateAppConfigPath = "$TemplateFolderPath\appconfig.json" + $TemplateApiManagementPath = "$TemplateFolderPath\apimanagement.json" - ## This is the Azure Key Vault Key used to store the Connection String to the Storage Account - Write-Verbose -Message "Retrieving the Azure Tenant and User Id Information" - $AzContext = $(Get-AzContext) - $AzTenantID = $AzContext.Tenant.Id - Write-Verbose -Message "Retrieved the Azure Tenant Id: $AzTenantID" + $ParameterAppInsightsPath = "$ParameterFolderPath\applicationinsights.json" + $ParameterKeyVaultPath = "$ParameterFolderPath\keyvault.json" + $ParameterStorageAccountPath = "$ParameterFolderPath\storageaccount.json" + $ParameterASPPath = "$ParameterFolderPath\asp.json" + $ParameterCDBAccountPath = "$ParameterFolderPath\cosmosdb.json" + $ParameterCDBPath = "$ParameterFolderPath\cosmosdb-sql.json" + $ParameterCDBContainerPath = "$ParameterFolderPath\cosmosdb-sql-container.json" + $ParameterFunctionPath = "$ParameterFolderPath\azurefunction.json" + $ParameterAppConfigPath = "$ParameterFolderPath\appconfig.json" + $ParameterApiManagementPath = "$ParameterFolderPath\apimanagement.json" - if ($AzContext.Account.type -eq "User") - { - $AzObjectID = $(Get-AzADUser -SignedIn).Id + Write-Verbose -Message "ARM Parameter Resource performance is based on the: $ImplementationPerformance." + + $PrimaryRegionName = $(Get-AzLocation).Where({$_.Location -eq $Region}).DisplayName + $SecondaryRegion = Get-PairedAzureRegion -Region $Region + $SecondaryRegionName = $(Get-AzLocation).Where({$_.Location -eq $SecondaryRegion}).DisplayName + + Write-Verbose -Message "Retrieving the Azure Tenant and User Information" + $AzContext = Get-AzContext + $AzTenantID = $AzContext.Tenant.Id + $AzTenantDomain = $AzContext.Tenant.Domains[0] + if ($AzContext.Account.Type -eq "User") + { + $AzObjectID = $(Get-AzADUser -SignedIn).Id + if (!$PublisherEmail) { + $PublisherEmail = $AzContext.Account.Id } - else - { - $AzObjectID = $(Get-AzADServicePrincipal -ApplicationId $AzContext.Account.ID).Id + if (!$PublisherName) { + $PublisherName = $AzContext.Account.Id } - - Write-Verbose -Message "Retrieved the Azure Object Id: $AzObjectID" - - ## This is specific to the JSON file creation - $JSONContentVersion = "1.0.0.0" - $JSONSchema = "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#" } - PROCESS + else { - Write-Verbose -Message "Validating that the inputs for the AppInsights template are not null." - if(!($AppInsightsName -and $ParameterAppInsightsPath -and $TemplateAppInsightsPath -and $JSONSchema -and $JSONContentVersion)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." - } - - Write-Verbose -Message "Validating that the inputs for the Keyvault template are not null." - if(!($KeyVaultName -and $ParameterKeyVaultPath -and $TemplateAppInsightsPath -and $JSONSchema -and $JSONContentVersion -and $KeyVaultName -and $KeyVaultSKU -and $AzObjectID -and $AzTenantID)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." + $AzObjectID = $(Get-AzADServicePrincipal -ApplicationId $AzContext.Account.ID).Id + if (!$PublisherEmail) { + $PublisherEmail = "WinGetRestSource@$AzTenantDomain" } - - Write-Verbose -Message "Validating that the inputs for the StorageAccount template are not null." - if(!($StorageAccountName -and $ParameterStorageAccountPath -and $TemplateStorageAccountPath -and $JSONSchema -and $JSONContentVersion -and $Region -and $StorageAccountName -and $StorageAccountPerformance)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." - } - - Write-Verbose -Message "Validating that the inputs for the asp template are not null." - if(!($aspName -and $ParameterASPPath -and $TemplateASPPath -and $JSONSchema -and $JSONContentVersion -and $aspName -and $Region -and $ASPSKU)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." - } - - Write-Verbose -Message "Validating that the inputs for the CosmosDBAccount template are not null." - if(!($CDBAccountName -and $ParameterCDBAccountPath -and $TemplateCDBAccountPath -and $JSONSchema -and $JSONContentVersion -and $CDBAccountName -and $null -ne $CosmosDBAEnableFreeTier -and $PrimaryRegionName -and $SecondaryRegionName)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." + if (!$PublisherName) { + $PublisherName = "WinGetRestSource@$AzTenantDomain" } + } + Write-Verbose -Message "Retrieved the Azure Object Id: $AzObjectID" - Write-Verbose -Message "Validating that the inputs for the CosmosDBDatabase template are not null." - if(!($CDBDatabaseName -and $ParameterCDBPath -and $TemplateCDBPath -and $JSONSchema -and $JSONContentVersion -and $CDBAccountName -and $CDBDatabaseName)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." + switch ($ImplementationPerformance) { + "Developer" { + $AppConfigSku = "Free" + $KeyVaultSKU = "Standard" + $StorageAccountPerformance = "Standard_LRS" + $AspSku = "B1" + $CosmosDBAEnableFreeTier = $true + ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + $CosmosDBACapabilities = "[]" + $CosmosDBAConsistency = "ConsistentPrefix" + $CosmosDBALocations = @( + @{ + locationName = $PrimaryRegionName + failoverPriority = 0 + isZoneRedundant = $false + } + ) + $ApiManagementSku = "Developer" } - - Write-Verbose -Message "Validating that the inputs for the CosmosDBContainer template are not null." - if(!($CDBContainerName -and $ParameterCDBContainerPath -and $TemplateCDBContainerPath -and $JSONSchema -and $JSONContentVersion -and $CDBAccountName -and $CDBDatabaseName -and $CDBContainerName)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." + "Basic" { + $AppConfigSku = "Standard" + $KeyVaultSKU = "Standard" + $StorageAccountPerformance = "Standard_GRS" + $AspSku = "S1" + $CosmosDBAEnableFreeTier = $false + ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + $CosmosDBACapabilities = "[]" + $CosmosDBAConsistency = "Session" + $CosmosDBALocations = @( + @{ + locationName = $PrimaryRegionName + failoverPriority = 0 + isZoneRedundant = $false + } + @{ + locationName = $SecondaryRegionName + failoverPriority = 1 + isZoneRedundant = $false + } + ) + $ApiManagementSku = "BasicV2" } - else - { - Write-Verbose -Message " inputs are not null." + "Enhanced" { + $AppConfigSku = "Premium" + $KeyVaultSKU = "Standard" + $StorageAccountPerformance = "Standard_GZRS" + $AspSku = "P1V2" + $CosmosDBAEnableFreeTier = $false + ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + $CosmosDBACapabilities = "[]" + $CosmosDBAConsistency = "Strong" + $CosmosDBALocations = @( + @{ + locationName = $PrimaryRegionName + failoverPriority = 0 + isZoneRedundant = $false + } + @{ + locationName = $SecondaryRegionName + failoverPriority = 1 + isZoneRedundant = $false + } + ) + $ApiManagementSku = "Standardv2" } + } - Write-Verbose -Message "Validating that the inputs for the Function template are not null." - if(!($FunctionName -and $ParameterFunctionPath -and $TemplateFunctionPath -and $JSONSchema -and $JSONContentVersion -and $AzKVStorageSecretName -and $Region -and $CDBDatabaseName -and $CDBContainerName -and $aspName -and $FunctionName -and $KeyVaultName -and $AppInsightsName)) - { - Write-Verbose -Message " Required values are null" - Write-Error -Message " Required values are null..." - } - else - { - Write-Verbose -Message " inputs are not null." - } + ## This is specific to the JSON file creation + $JSONContentVersion = "1.0.0.0" + $JSONSchema = "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#" - ## Creates a PowerShell object array to contain the details of the Parameter files. - $ARMObjects = @( - @{ ObjectType = "AppInsight" - ObjectName = $AppInsightsName - ParameterPath = "$ParameterAppInsightsPath" - TemplatePath = "$TemplateAppInsightsPath" - Error = "" + ## Creates a PowerShell object array to contain the details of the Parameter files. + $ARMObjects = @( + @{ ObjectType = "AppInsight" + ObjectName = $AppInsightsName + ParameterPath = "$ParameterAppInsightsPath" + TemplatePath = "$TemplateAppInsightsPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - Name = @{ value = $AppInsightsName } - } + name = @{ value = $AppInsightsName } + location = @{ value = $Region } } - }, - @{ ObjectType = "Keyvault" - ObjectName = $KeyVaultName - ParameterPath = "$ParameterKeyVaultPath" - TemplatePath = "$TemplateKeyVaultPath" - Error = "" + } + }, + @{ ObjectType = "Keyvault" + ObjectName = $KeyVaultName + ParameterPath = "$ParameterKeyVaultPath" + TemplatePath = "$TemplateKeyVaultPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - name = @{ - value = $KeyVaultName - type = "string" - } - sku = @{ value = $KeyVaultSKU} - accessPolicies = @{ - value = @( - @{ - tenantId = $AzTenantID - objectID = $AzObjectID - permissions = @{ - keys = @() - secrets = @( "Get", "Set" ) - certificates = @() - } + name = @{ value = $KeyVaultName } + location = @{ value = $Region } + sku = @{ value = $KeyVaultSKU} + accessPolicies = @{ + value = @( + @{ + tenantId = $AzTenantID + objectID = $AzObjectID + permissions = @{ + keys = @() + secrets = @( "Get", "Set" ) + certificates = @() } - ) - } + } + ) } } - }, - @{ ObjectType = "AppConfig" - ObjectName = $FunctionName - ParameterPath = "$ParameterAppConfigPath" - TemplatePath = "$TemplateAppConfigPath" - Error = "" + } + }, + @{ ObjectType = "AppConfig" + ObjectName = $FunctionName + ParameterPath = "$ParameterAppConfigPath" + TemplatePath = "$TemplateAppConfigPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - appConfigName = @{ value = $appConfigName } # Name used to contain the Storage Account connection string in the Key Value - location = @{ value = $Region } # Azure hosting location - } + appConfigName = @{ value = $appConfigName } # Name used to contain the Storage Account connection string in the Key Value + location = @{ value = $Region } # Azure hosting location + sku = @{ value = $AppConfigSku } } - }, - @{ ObjectType = "StorageAccount" - ObjectName = $StorageAccountName - ParameterPath = "$ParameterStorageAccountPath" - TemplatePath = "$TemplateStorageAccountPath" - Error = "" + } + }, + @{ ObjectType = "StorageAccount" + ObjectName = $StorageAccountName + ParameterPath = "$ParameterStorageAccountPath" + TemplatePath = "$TemplateStorageAccountPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - location = @{ value = $Region } - storageAccountName = @{ value = $StorageAccountName } - accountType = @{ value = $StorageAccountPerformance } - } + location = @{ value = $Region } + storageAccountName = @{ value = $StorageAccountName } + accountType = @{ value = $StorageAccountPerformance } } - }, - @{ ObjectType = "asp" - ObjectName = $aspName - ParameterPath = "$ParameterASPPath" - TemplatePath = "$TemplateASPPath" - Error = "" + } + }, + @{ ObjectType = "asp" + ObjectName = $aspName + ParameterPath = "$ParameterASPPath" + TemplatePath = "$TemplateASPPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - aspName = @{ value = $aspName } - location = @{ value = $Region } - skuCode = @{ value = $ASPSKU } - numberOfWorkers = @{ value = "1" } - } + aspName = @{ value = $aspName } + location = @{ value = $Region } + skuCode = @{ value = $AspSku } } - }, - @{ ObjectType = "CosmosDBAccount" - ObjectName = $CDBAccountName - ParameterPath = "$ParameterCDBAccountPath" - TemplatePath = "$TemplateCDBAccountPath" - Error = "" + } + }, + @{ ObjectType = "CosmosDBAccount" + ObjectName = $CDBAccountName + ParameterPath = "$ParameterCDBAccountPath" + TemplatePath = "$TemplateCDBAccountPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - name = @{ value = $CDBAccountName } - enableFreeTier = @{ value = $CosmosDBAEnableFreeTier } - tags = @{ - value = @{ - defaultExperience = "Core (SQL)" - CosmosAccountType = "Production" - } + name = @{ value = $CDBAccountName } + location = @{ value = $Region } + enableFreeTier = @{ value = $CosmosDBAEnableFreeTier } + consistencyPolicy = @{ + value = @{ + defaultConsistencyLevel = $CosmosDBAConsistency + maxIntervalInSeconds = 5 + maxStalenessPrefix = 100 } - consistencyPolicy = @{ - value = @{ - defaultConsistencyLevel = "ConsistentPrefix" - maxIntervalInSeconds = 5 - maxStalenessPrefix = 100 + } + locations = @{ value = $CosmosDBALocations } + # Allows requests from azure portal and Azure datacenter ip range (0.0.0.0) + ipRules = @{ + value = @( + @{ + ipAddressOrRange = "13.91.105.215" } - } - locations = @{ - value = @( - @{ - locationName = $PrimaryRegionName - failoverPriority = 0 - isZoneRedundant = $false - } - @{ - locationName = $SecondaryRegionName - failoverPriority = 1 - isZoneRedundant = $false - } - ) - } - # Allows requests from azure portal and Azure datacenter ip range (0.0.0.0) - ipRules =@{ - value = @( - @{ - ipAddressOrRange = "13.91.105.215" - } - @{ - ipAddressOrRange = "4.210.172.107" - } - @{ - ipAddressOrRange = "13.88.56.148" - } - @{ - ipAddressOrRange = "40.91.218.243" - } - @{ - ipAddressOrRange = "0.0.0.0" - } - ) - } - backupPolicy = @{ - value = @{ - type = "Periodic" - periodicModeProperties = @{ - backupIntervalInMinutes = 240 - backupRetentionIntervalInHours = 720 - } + @{ + ipAddressOrRange = "4.210.172.107" + } + @{ + ipAddressOrRange = "13.88.56.148" + } + @{ + ipAddressOrRange = "40.91.218.243" + } + @{ + ipAddressOrRange = "0.0.0.0" + } + ) + } + backupPolicy = @{ + value = @{ + type = "Periodic" + periodicModeProperties = @{ + backupIntervalInMinutes = 240 + backupRetentionIntervalInHours = 720 } } } } - }, - @{ ObjectType = "CosmosDBDatabase" - ObjectName = $CDBDatabaseName - ParameterPath = "$ParameterCDBPath" - TemplatePath = "$TemplateCDBPath" - Error = "" + } + }, + @{ ObjectType = "CosmosDBDatabase" + ObjectName = $CDBDatabaseName + ParameterPath = "$ParameterCDBPath" + TemplatePath = "$TemplateCDBPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - cosmosName = @{ value = $CDBAccountName } - sqlname = @{ value = $CDBDatabaseName } - options = @{ - Value = @{ - autoscaleSettings = @{ - maxThroughput = 4000 - } + cosmosName = @{ value = $CDBAccountName } + sqlname = @{ value = $CDBDatabaseName } + options = @{ + Value = @{ + autoscaleSettings = @{ + maxThroughput = 4000 } } } } - }, - @{ ObjectType = "CosmosDBContainer" - ObjectName = $CDBContainerName - ParameterPath = "$ParameterCDBContainerPath" - TemplatePath = "$TemplateCDBContainerPath" - Error = "" + } + }, + @{ ObjectType = "CosmosDBContainer" + ObjectName = $CDBContainerName + ParameterPath = "$ParameterCDBContainerPath" + TemplatePath = "$TemplateCDBContainerPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - cosmosName = @{ Value = $CDBAccountName } - sqlname = @{ Value = $CDBDatabaseName } - containerName = @{ value = $CDBContainerName} - indexingPolicy = @{ Value = @{ - IndexingMode = "consistent" - automatic = $true - includedPaths = @(@{ - path = "/*" - }) - excludePaths = @(@{ - path = '/"_etag"/?' - }) - }} - partitionKey = @{ - value = @{ - paths = @("/id") - kind = "Hash" - } + cosmosName = @{ Value = $CDBAccountName } + sqlname = @{ Value = $CDBDatabaseName } + containerName = @{ value = $CDBContainerName} + indexingPolicy = @{ Value = @{ + IndexingMode = "consistent" + automatic = $true + includedPaths = @(@{ + path = "/*" + }) + excludePaths = @(@{ + path = '/"_etag"/?' + }) + }} + partitionKey = @{ + value = @{ + paths = @("/id") + kind = "Hash" } - conflictResolutionPolicy = @{ - value = @{ - mode = "LastWriterWins" - conflictResolutionPath = "/_ts" - } + } + conflictResolutionPolicy = @{ + value = @{ + mode = "LastWriterWins" + conflictResolutionPath = "/_ts" } } } - }, - @{ ObjectType = "Function" - ObjectName = $FunctionName - ParameterPath = "$ParameterFunctionPath" - TemplatePath = "$TemplateFunctionPath" - Error = "" + } + }, + @{ ObjectType = "Function" + ObjectName = $FunctionName + ParameterPath = "$ParameterFunctionPath" + TemplatePath = "$TemplateFunctionPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion Parameters = @{ - '$Schema' = $JSONSchema - contentVersion = $JSONContentVersion - Parameters = @{ - storageSecretName = @{ value = $AzKVStorageSecretName } # Name used to contain the Storage Account connection string in the Key Value - location = @{ value = $Region } # Azure hosting location - cosmosDatabase = @{ value = $CDBDatabaseName } # Cosmos Database Name - cosmosContainer = @{ value = $CDBContainerName } # Cosmos Container Name - serverIdentifier = @{ value = $aspName } # Azure Function Name - functionName = @{ value = $FunctionName } # Azure Function Name - appServiceName = @{ value = $aspName } # Azure App Service Name - keyVaultName = @{ value = $KeyVaultName } # Azure Keyvault Name - appInsightName = @{ value = $AppInsightsName } # Azure App Insights Name - manifestCacheEndpoint = @{ value = $manifestCacheEndpoint } # Not suported - monitoringTenant = @{ value = $monitoringTenant } # Not suported - monitoringRole = @{ value = $monitoringRole } # Not suported - monitoringMetricsAccount = @{ value = $monitoringMetricsAccount } # Not suported - } + location = @{ value = $Region } # Azure hosting location + cosmosDatabase = @{ value = $CDBDatabaseName } # Cosmos Database Name + cosmosContainer = @{ value = $CDBContainerName } # Cosmos Container Name + serverIdentifier = @{ value = $ServerIdentifier } # Azure Function Server Identifier + functionName = @{ value = $FunctionName } # Azure Function Name + appServiceName = @{ value = $aspName } # Azure App Service Name + keyVaultName = @{ value = $KeyVaultName } # Azure Keyvault Name + azFuncStorageName = @{ value = $StorageAccountName } # Azure Storage Account Name + appInsightName = @{ value = $AppInsightsName } # Azure App Insights Name + manifestCacheEndpoint = @{ value = $manifestCacheEndpoint } # Not suported + monitoringTenant = @{ value = $monitoringTenant } # Not suported + monitoringRole = @{ value = $monitoringRole } # Not suported + monitoringMetricsAccount = @{ value = $monitoringMetricsAccount } # Not suported + runFromPackageUrl = @{ value = $RunFromPackageUrl } # Not suported + serverAuthenticationType = @{ value = $ServerAuthenticationType } # Server authentication type + microsoftEntraIdResource = @{ value = $MicrosoftEntraIdResource } # Microsoft Entra Id Resource + microsoftEntraIdResourceScope = @{ value = $MicrosoftEntraIdResourceScope } # Microsoft Entra Id Resource Scope + } + } + }, + @{ ObjectType = "ApiManagement" + ObjectName = $ApiManagementName + ParameterPath = "$ParameterApiManagementPath" + TemplatePath = "$TemplateApiManagementPath" + Error = "" + Parameters = @{ + '$Schema' = $JSONSchema + contentVersion = $JSONContentVersion + Parameters = @{ + serviceName = @{ value = $ApiManagementName } + publisherEmail = @{ value = $PublisherEmail } + publisherName = @{ value = $PublisherName } + sku = @{ value = $ApiManagementSku } + location = @{ value = $Region } + keyVaultName = @{ value = $KeyVaultName } + backendUrls = @{ value = @() } # Value to be populated after Azure Function is created + queryApiValidationEnabled = @{ value = $QueryApiValidationEnabled } + microsoftEntraIdResource = @{ value = $MicrosoftEntraIdResource } } - } - ) + } + } + ) - ## Uses the newly created ARMObjects[#].Parameters to create new JSON Parameter files. - Write-Verbose -Message "Creating JSON Parameter files for Azure Object Creation:" + ## Uses the newly created ARMObjects[#].Parameters to create new JSON Parameter files. + Write-Verbose -Message "Creating JSON Parameter files for Azure Object Creation:" - ## Creates each JSON Parameter file inside of a Parameter folder in the working directory - foreach ($object in $ARMObjects) { - ## Converts the structure of the variable to a JSON file. - Write-Verbose -Message " Creating the Parameter file for $($Object.ObjectType) in the following location:`n $($Object.ParameterPath)" - $parameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 - $parameterFile| Out-File -FilePath $Object.ParameterPath -Force - Write-Verbose -Message "Parameter file: ($parameterFile)" - } - } - END - { - ## Returns the completed object. - return $ARMObjects + ## Creates each JSON Parameter file inside of a Parameter folder in the working directory + foreach ($object in $ARMObjects) { + ## Converts the structure of the variable to a JSON file. + Write-Verbose -Message " Creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" + $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 + $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force } + + ## Returns the completed object. + return $ARMObjects } \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index a6b6ba2f..98399dff 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -31,6 +31,12 @@ Function New-WinGetSource .PARAMETER RestSourcePath [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip) + .PARAMETER PublisherName + [Optional] The WinGet rest source publisher name + + .PARAMETER PublisherEmail + [Optional] The WinGet rest source publisher email + .PARAMETER ImplementationPerformance [Optional] ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. | Preference | Description | @@ -41,6 +47,18 @@ Function New-WinGetSource (Default: Basic) + .PARAMETER RestSourceAuthentication + [Optional] ["None", "MicrosoftEntraId"] The WinGet rest source authentication type. [Default: None] + + .PARAMETER CreateNewMicrosoftEntraIdAppRegistration + [Optional] If specified, a new Microsoft Entra Id app registration will be created. (Default: False) + + .PARAMETER MicrosoftEntraIdResource + [Optional] Microsoft Entra Id authentication resource + + .PARAMETER MicrosoftEntraIdResourceScope + [Optional] Microsoft Entra Id authentication resource scope + .PARAMETER ShowConnectionInstructions [Optional] If specified, the instructions for connecting to the Windows Package Manager REST source. (Default: False) @@ -175,14 +193,13 @@ Function New-WinGetSource ############################### ## Shows how to connect local Windows Package Manager Client to newly created REST source if($ShowConnectionInstructions) { - $jsonFunction = Get-Content -Path $($ARMObjects.Where({$_.ObjectType -eq "Function"}).ParameterPath) | ConvertFrom-Json - $AzFunctionName = $jsonFunction.Parameters.FunctionName.Value - $AzFunctionURL = $(Get-AzFunctionApp -Name $AzFunctionName -ResourceGroupName $ResourceGroup).DefaultHostName + $ApiManagementName = $ARMObjects.Where({$_.ObjectType -eq "ApiManagement"}).Parameters.Parameters.serviceName.value + $ApiManagementURL = $(Get-AzApiManagement -Name $ApiManagementName -ResourceGroupName $ResourceGroup).RuntimeUrl ## Post script Run Informational: #### Instructions on how to add the REST source to your Windows Package Manager Client Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" -InformationAction Continue - Write-Information -MessageData " winget source add -n ""restsource"" -a ""https://$AzFunctionURL/api/"" -t ""Microsoft.Rest""" -InformationAction Continue + Write-Information -MessageData " winget source add -n ""restsource"" -a ""https://$ApiManagementURL/api/"" -t ""Microsoft.Rest""" -InformationAction Continue #### For more information about how to use the solution, visit the aka.ms link. Write-Information -MessageData "`nFor more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" -InformationAction Continue diff --git a/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json b/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json index 84c90eda..53066f42 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json @@ -27,6 +27,13 @@ "metadata": { "description": "List of feature flags to set. These are specifically for the feature management tools in App Config, not key-vaule pairs." } + }, + "sku": { + "type": "string", + "defaultValue": "Standard", + "metadata": { + "description": "Specifies the sku to be used." + } } }, "resources": [ @@ -36,7 +43,7 @@ "type": "Microsoft.AppConfiguration/configurationStores", "location": "[parameters('location')]", "sku": { - "name": "standard" + "name": "[parameters('sku')]" }, "properties": { "disableLocalAuth": true diff --git a/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb-sql-container.json b/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb-sql-container.json index 008c325e..ae33fdfe 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb-sql-container.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb-sql-container.json @@ -47,7 +47,7 @@ "description": "Options." } }, - "options": { + "options": { "type": "object", "defaultValue": {}, "metadata": { @@ -73,4 +73,4 @@ } } ] -} \ No newline at end of file +} diff --git a/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb.json b/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb.json index a0998258..494aae24 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/CosmosDB/cosmosdb.json @@ -34,8 +34,8 @@ "defaultValue": { "type": "None" }, - "metadata":{ - "description": "Tags." + "metadata": { + "description": "Identity." } }, "publicNetworkAccess": { @@ -179,4 +179,4 @@ } } ] -} \ No newline at end of file +} From 0130bc2a870891d19cc83a6540e4bd028bc096e5 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:44:22 -0800 Subject: [PATCH 13/16] pr comments, e2e fixes and make operations compatible with "update" calls --- .../src/Library/Add-WinGetManifest.ps1 | 10 +- .../src/Library/Connect-ToAzure.ps1 | 2 +- .../src/Library/Find-WinGetManifest.ps1 | 10 +- .../src/Library/Get-WinGetManifest.ps1 | 12 +- .../src/Library/New-ARMObjects.ps1 | 142 ++++++++++++------ .../src/Library/New-ARMParameterObjects.ps1 | 25 +-- .../src/Library/New-MicrosoftEntraIdApp.ps1 | 36 ++--- .../src/Library/New-WinGetSource.ps1 | 24 +-- .../src/Library/Remove-WinGetManifest.ps1 | 10 +- .../src/Library/Test-ARMTemplates.ps1 | 4 +- .../src/Library/Test-AzureResource.ps1 | 93 ------------ .../src/Library/Test-WinGetManifest.ps1 | 6 +- .../Templates/AppConfig/appconfig.json | 10 +- .../AzureFunction/azurefunction.json | 54 +------ 14 files changed, 162 insertions(+), 276 deletions(-) delete mode 100644 Tools/PowershellModule/src/Library/Test-AzureResource.ps1 diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index c6d28651..a6f15c0b 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -66,14 +66,6 @@ Function Add-WinGetManifest Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - ############################### - ## Verify Azure Resources Exist - Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName - if(!$Result) { - Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop - } - ############################################# ############## REST api call ############## @@ -85,7 +77,7 @@ Function Add-WinGetManifest ## Retrieves the Azure Function URL used to add new manifests to the REST source Write-Verbose -Message "Retrieving the Azure Function $FunctionName to build out the REST API request." - $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName -Name $FunctionName $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index b8ee708b..6325e7e1 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -54,7 +54,7 @@ Function Connect-ToAzure Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Name $SubscriptionName" $TestAzureConnection = Test-ConnectionToAzure -SubscriptionName $SubscriptionName } - elseif($SubscriptionId -and $TestAzureConnection){ + elseif($SubscriptionId){ ## If connected to Azure, and the Subscription Id are provided then verify that the connected Azure session matches the provided Subscription Id. Write-Verbose -Message "Verifying if PowerShell session is currently connected to your Azure Subscription Id $SubscriptionId" $TestAzureConnection = Test-ConnectionToAzure -SubscriptionId $SubscriptionId diff --git a/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 index 41f58aaa..baee1c68 100644 --- a/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 @@ -73,17 +73,9 @@ Function Find-WinGetManifest Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - ############################### - ## Verify Azure Resources Exist - Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName - if(!$Result) { - Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop - } - ## Retrieves the Azure Function URL used to add new manifests to the REST source Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." - $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName -Name $FunctionName $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 0e462b29..4211d089 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -72,7 +72,7 @@ Function Get-WinGetManifest ############################### ## Determines the PowerShell Parameter Set that was used in the call of this Function. switch ($PsCmdlet.ParameterSetName) { - "Azure" { + "Azure" { ############################### ## Connects to Azure, if not already connected. Write-Verbose "Testing connection to Azure." @@ -89,17 +89,9 @@ Function Get-WinGetManifest Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - ############################### - ## Verify Azure Resources Exist - Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName - if(!$Result) { - Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop - } - ## Retrieves the Azure Function URL used to add new manifests to the REST source Write-Verbose -Message "Retrieving Azure Function Web Applications matching to: $FunctionName." - $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName -Name $FunctionName $FunctionAppId = $FunctionApp.Id $DefaultHostName = $FunctionApp.DefaultHostName diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index c0060137..1057a5af 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -30,8 +30,8 @@ Function New-ARMObjects [Parameter(Position=2, Mandatory=$true)] [string] $ResourceGroup ) - # Function to Create Random Strings - function Create-AppKey() + # Function to create a new Function App key + function New-FunctionAppKey { $private:characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHIJKLMENOPTSTUVWXYZ' $private:randomChars = 1..64 | ForEach-Object { Get-Random -Maximum $characters.length } @@ -41,6 +41,24 @@ Function New-ARMObjects return [String]$characters[$randomChars] } + # Function to ensure Azure role assignment + function Set-RoleAssignment + { + param( + [string] $PrincipalId, + [string] $RoleName, + [string] $ResourceGroup, + [string] $ResourceName, + [string] $ResourceType + ) + + $GetAssignment = Get-AzRoleAssignment -ObjectId $PrincipalId -RoleDefinitionName $RoleName -ResourceGroupName $ResourceGroup -ResourceName $ResourceName -ResourceType $ResourceType + if (!$GetAssignment) { + Write-Verbose "Creating role assignment. Role: $RoleName" + New-AzRoleAssignment -ObjectId $PrincipalId -RoleDefinitionName $RoleName -ResourceGroupName $ResourceGroup -ResourceName $ResourceName -ResourceType $ResourceType + } + } + ## Azure resource names retrieved from the Parameter files. $StorageAccountName = $ARMObjects.Where({$_.ObjectType -eq "StorageAccount"}).Parameters.Parameters.storageAccountName.value $KeyVaultName = $ARMObjects.Where({$_.ObjectType -eq "Keyvault"}).Parameters.Parameters.name.value @@ -60,111 +78,105 @@ Function New-ARMObjects ## This is order specific, please ensure you used the New-ARMParameterObjects function to create this object in the pre-determined order. foreach ($Object in $ARMObjects) { - Write-Information " Creating the Azure Object - $($Object.ObjectType)" + Write-Information "Creating the Azure Object - $($Object.ObjectType)" ## If the object to be created is an Azure Function, then complete these pre-required steps before creating the Azure Function. if ($Object.ObjectType -eq "Function") { - Write-Verbose " Creating KeyVault Secrets:" - $CosmosAccountEndpointValue = $(Get-AzCosmosDBAccount -Name $CosmosAccountName -ResourceGroupName $ResourceGroup).DocumentEndpoint | ConvertTo-SecureString -AsPlainText -Force - Write-Verbose " Creating Keyvault Secret for Azure CosmosDB endpoint." + Write-Verbose "Creating Keyvault Secret for Azure CosmosDB endpoint." Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $CosmosAccountEndpointKeyName -SecretValue $CosmosAccountEndpointValue - $AppConfigEndpointValue = $(Get-AzAppConfigurationStore -Name azpstest-appstore -ResourceGroupName $ResourceGroup).Endpoint | ConvertTo-SecureString -AsPlainText -Force - Write-Verbose " Creating Keyvault Secret for Azure App Config endpoint." + $AppConfigEndpointValue = $(Get-AzAppConfigurationStore -Name $AppConfigName -ResourceGroupName $ResourceGroup).Endpoint | ConvertTo-SecureString -AsPlainText -Force + Write-Verbose "Creating Keyvault Secret for Azure App Config endpoint." Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AppConfigPrimaryEndpointName -SecretValue $AppConfigEndpointValue Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AppConfigSecondaryEndpointName -SecretValue $AppConfigEndpointValue } elseif ($Object.ObjectType -eq "ApiManagement") { ## Create instance manually if not exist $ApiManagementParameters = $Object.Parameters.Parameters - $ApiManagement = Get-AzApiManagement -ResourceGroupName $ResourceGroup -Name $ApiManagementParameters.serviceName.value + $ApiManagement = Get-AzApiManagement -ResourceGroupName $ResourceGroup -Name $ApiManagementParameters.serviceName.value -ErrorVariable ErrorGet -ErrorAction SilentlyContinue if (!$ApiManagement) { - Write-Information "Creating new Api Aanagement service. Name: $($ApiManagementParameters.serviceName.value)" + Write-Warning "Creating new Api Aanagement service. Name: $($ApiManagementParameters.serviceName.value)" + Write-Warning "This is a long-running action. It can take between 30 and 40 minutes to create and activate an API Management service." $ApiManagement = New-AzApiManagement -ResourceGroupName $ResourceGroup -Name $ApiManagementParameters.serviceName.value -Location $ApiManagementParameters.location.value -Organization $ApiManagementParameters.publisherName.value -AdminEmail $ApiManagementParameters.publisherEmail.value -Sku $ApiManagementParameters.sku.value -SystemAssignedIdentity -ErrorVariable DeployError if ($DeployError) { - Write-Error "Failed to create Api Aanagement service. $DeployError" + Write-Error "Failed to create Api Aanagement service. Error: $DeployError" return $false } } ## Update backend urls and re-create parameters file $ApiManagementParameters.backendUrls.value = $FunctionAppUrls - Write-Verbose -Message " Re-creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" + Write-Verbose -Message "Re-creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force ## Set secret get for Api management service + Write-Verbose "Set keyvault secret access for Api Management service" Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $ApiManagement.Identity.PrincipalId -PermissionsToSecrets Get } ## Creates the Azure Resource - $DeployJob = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable DeployError -AsJob - - while ($DeployJob.State -eq "Running") { - ## Sets a sleep of 2 seconds after object creation to allow Azure to update creation status, and mark as "running" - Start-Sleep -Seconds 2 - } + $DeployResult = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable DeployError ## Verifies that no error occured when creating the Azure resource - if ($DeployError -or $DeployJob.State -ne "Completed") { + if ($DeployError -or ($DeployResult.ProvisioningState -ne "Succeeded" -and $DeployResult.ProvisioningState -ne "Created")) { $ErrReturnObject = @{ DeployError = $DeployError - JobError = $DeployJob + DeployResult = $DeployResult } - ## TODO: extend error reporting in logs across scripts. - Write-Error "Failed to create Azure object. $DeployError" -TargetObject $ErrReturnObject + Write-Error "Failed to create Azure object. Error: $DeployError" -TargetObject $ErrReturnObject return $false } - Write-Information -MessageData " $($Object.ObjectType) was successfully created." + Write-Information -MessageData "$($Object.ObjectType) was successfully created." - ## Publish GitHub Functions to newly created Azure Function if($Object.ObjectType -eq "Function") { + ## Publish GitHub Functions to newly created Azure Function $FunctionName = $Object.Parameters.Parameters.functionName.value $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroup -Name $FunctionName $FunctionAppId = $FunctionApp.Id $FunctionAppUrls += "https://$($FunctionApp.DefaultHostName)/api" + ## Assign necessary Azure roles + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Account Contributor" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Blob Data Owner" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Table Data Contributor" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Queue Data Contributor" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Queue Data Message Processor" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Queue Data Message Sender" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" + Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "App Configuration Data Reader" -ResourceGroup $ResourceGroup -ResourceName $AppConfigName -ResourceType "Microsoft.AppConfiguration/configurationStores" + + ## Assign cosmos db roles + $CosmosAccount = Get-AzCosmosDBAccount -ResourceGroupName $ResourceGroup -Name $CosmosAccountName + $RoleId = "00000000-0000-0000-0000-000000000002" # Built in contributor role + $RoleDefinitionId = "$($CosmosAccount.Id)/sqlRoleAssignments/$RoleId" + if ((Get-AzCosmosDBSqlRoleAssignment -ResourceGroupName $ResourceGroup -AccountName $CosmosAccountName).Where({$_.PrincipalId -eq $FunctionApp.IdentityPrincipalId -and $_.RoleDefinitionId -eq $RoleDefinitionId}).Count -eq 0) { + Write-Verbose "Assigning Cosmos DB Account contributor role" + New-AzCosmosDBSqlRoleAssignment -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -RoleDefinitionId $RoleId -Scope "/" -PrincipalId $FunctionApp.IdentityPrincipalId + } + ## Create Function app key and also add to keyvault - $local:NewFunctionKeyValue = Create-AppKey + $NewFunctionKeyValue = New-FunctionAppKey $Result = Invoke-AzRestMethod -Path "$FunctionAppId/host/default/functionKeys/WinGetRestSourceAccess?api-version=2024-04-01" -Method PUT -Payload (@{properties=@{value = $NewFunctionKeyValue}} | ConvertTo-Json -Depth 8) if ($Result.StatusCode -ne 200 -and $Result.StatusCode -ne 201) { Write-Error "Failed to create Azure Function key. $($Result.Content)" return $false } - Write-Information -MessageData " Add Function App host key to keyvault." + Write-Information -MessageData "Add Function App host key to keyvault." Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AzureFunctionHostKeyName -SecretValue ($NewFunctionKeyValue | ConvertTo-SecureString -AsPlainText -Force) - ## Assign necessary roles - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Account Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Blob Data Owner" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Table Data Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Contributor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Message Processor" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "Storage Queue Data Message Sender" -ResourceGroupName $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" - New-AzRoleAssignment -ObjectId $FunctionApp.Identity.PrincipalId -RoleDefinitionName "App Configuration Data Reader" -ResourceGroupName $ResourceGroup -ResourceName $AppConfigName -ResourceType "Microsoft.AppConfiguration/configurationStores" - - ## Assign cosmos db roles - $RoleId = [guid]::NewGuid().ToString() - $CosmosDBDataAction = @("Microsoft.DocumentDB/databaseAccounts/readMetadata", "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*", "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*") - New-AzCosmosDBSqlRoleDefinition -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -Type CustomRole -RoleName "ReadWriteAll" -AssignableScope "/" -DataAction $CosmosDBDataAction -Id $RoleId - New-AzCosmosDBSqlRoleAssignment -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -RoleDefinitionId $RoleId -Scope "/" -PrincipalId $FunctionApp.Identity.PrincipalId - - Write-Information -MessageData " Publishing function files to the Azure Function." - $DeployJob = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $FunctionName -Force -AsJob -ErrorVariable DeployError - while ($DeployJob.State -eq "Running") { - Start-Sleep -Seconds 2 - } + Write-Information -MessageData "Publishing function files to the Azure Function." + $DeployResult = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $FunctionName -Force -ErrorVariable DeployError ## Verifies that no error occured when publishing the Function App - if ($DeployError -or $DeployJob.State -ne "Completed") { + if ($DeployError -or !$DeployResult) { $ErrReturnObject = @{ DeployError = $DeployError - JobError = $DeployJob + DeployResult = $DeployResult } Write-Error "Failed to publishing the Function App. $DeployError" -TargetObject $ErrReturnObject @@ -174,6 +186,40 @@ Function New-ARMObjects ## Restart the Function App Restart-AzFunctionApp -Name $FunctionName -ResourceGroupName $ResourceGroup -Force } + elseif ($Object.ObjectType -eq "AppConfig") { + ## Update App Config values if needed + $AppConfigParameters = $Object.Parameters.Parameters + if (!$AppConfigParameters.deployAppConfigValues.value) { + ## This is local deployment + Write-Information "Updating App Config values" + + ## Assign Data Owner role + $AzContext = Get-AzContext + if ($AzContext.Account.Type -eq "User") { + $AzObjectID = $(Get-AzADUser -SignedIn).Id + } + else { + $AzObjectID = $(Get-AzADServicePrincipal -ApplicationId $AzContext.Account.ID).Id + } + Set-RoleAssignment -PrincipalId $AzObjectID -RoleName "App Configuration Data Owner" -ResourceGroup $ResourceGroup -ResourceName $AppConfigParameters.appConfigName.value -ResourceType "Microsoft.AppConfiguration/configurationStores" + + $AppConfig = Get-AzAppConfigurationStore -ResourceGroupName $ResourceGroup -Name $AppConfigParameters.appConfigName.value + $Path = "$($AppConfig.Id)?api-version=2022-05-01" + $Body = @{ properties = @{ disableLocalAuth = $false } } | ConvertTo-Json -Depth 8 + Invoke-AzRestMethod -Method Patch -Path $Path -Payload $Body + + ## Updating config values. Currently only feature flags. + $FeatureFlagValue = @{ + id = "GenevaLogging" + description = "Feature flag to use Geneva Monitoring." + enabled = $false + conditions = @{ client_filters = @() } } | ConvertTo-Json -Depth 8 + Set-AzAppConfigurationKeyValue -Endpoint $AppConfig.Endpoint -Key ".appconfig.featureflag/GenevaLogging" -Value $FeatureFlagValue + + ## Re-enable disable local auth + Update-AzAppConfigurationStore -ResourceGroupName $ResourceGroup -Name $AppConfigParameters.appConfigName.value -DisableLocalAuth + } + } } return $true diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 index c62065fe..48f33028 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 @@ -52,13 +52,13 @@ Function New-ARMParameterObjects [Parameter(Position=1, Mandatory=$true)] [string]$TemplateFolderPath, [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, - [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance + [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance, [Parameter(Position=5, Mandatory=$false)] [string]$PublisherName = "", [Parameter(Position=6, Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("None", "MicrosoftEntraId")] [Parameter(Position=7, Mandatory=$false)] [string]$RestSourceAuthentication = "None", [Parameter(Position=8, Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", - [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", + [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "" ) $ARMObjects = @() @@ -134,6 +134,8 @@ Function New-ARMParameterObjects $AzContext = Get-AzContext $AzTenantID = $AzContext.Tenant.Id $AzTenantDomain = $AzContext.Tenant.Domains[0] + $DeployAppConfigValues = $false + if ($AzContext.Account.Type -eq "User") { $AzObjectID = $(Get-AzADUser -SignedIn).Id @@ -143,6 +145,8 @@ Function New-ARMParameterObjects if (!$PublisherName) { $PublisherName = $AzContext.Account.Id } + + $DeployAppConfigValues = $false } else { @@ -153,6 +157,8 @@ Function New-ARMParameterObjects if (!$PublisherName) { $PublisherName = "WinGetRestSource@$AzTenantDomain" } + + $DeployAppConfigValues = $true } Write-Verbose -Message "Retrieved the Azure Object Id: $AzObjectID" @@ -163,7 +169,7 @@ Function New-ARMParameterObjects $StorageAccountPerformance = "Standard_LRS" $AspSku = "B1" $CosmosDBAEnableFreeTier = $true - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + ## To enable Serverless then set CosmosDBACapatilities to "[{"name": ""EnableServerless""}]" $CosmosDBACapabilities = "[]" $CosmosDBAConsistency = "ConsistentPrefix" $CosmosDBALocations = @( @@ -181,7 +187,7 @@ Function New-ARMParameterObjects $StorageAccountPerformance = "Standard_GRS" $AspSku = "S1" $CosmosDBAEnableFreeTier = $false - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + ## To enable Serverless then set CosmosDBACapatilities to "[{"name": ""EnableServerless""}]" $CosmosDBACapabilities = "[]" $CosmosDBAConsistency = "Session" $CosmosDBALocations = @( @@ -204,7 +210,7 @@ Function New-ARMParameterObjects $StorageAccountPerformance = "Standard_GZRS" $AspSku = "P1V2" $CosmosDBAEnableFreeTier = $false - ## To enable Serverless then set CosmosDBACapatilities to "[{"name"; ""EnableServerless""}]" + ## To enable Serverless then set CosmosDBACapatilities to "[{"name": ""EnableServerless""}]" $CosmosDBACapabilities = "[]" $CosmosDBAConsistency = "Strong" $CosmosDBALocations = @( @@ -280,9 +286,10 @@ Function New-ARMParameterObjects '$Schema' = $JSONSchema contentVersion = $JSONContentVersion Parameters = @{ - appConfigName = @{ value = $appConfigName } # Name used to contain the Storage Account connection string in the Key Value - location = @{ value = $Region } # Azure hosting location - sku = @{ value = $AppConfigSku } + appConfigName = @{ value = $appConfigName } # Name used to contain the Storage Account connection string in the Key Value + location = @{ value = $Region } # Azure hosting location + sku = @{ value = $AppConfigSku } + deployAppConfigValues = @{ value = $DeployAppConfigValues } } } }, @@ -484,7 +491,7 @@ Function New-ARMParameterObjects ## Creates each JSON Parameter file inside of a Parameter folder in the working directory foreach ($object in $ARMObjects) { ## Converts the structure of the variable to a JSON file. - Write-Verbose -Message " Creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" + Write-Verbose -Message "Creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force } diff --git a/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 b/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 index 2ce6956f..ad094513 100644 --- a/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 +++ b/Tools/PowershellModule/src/Library/New-MicrosoftEntraIdApp.ps1 @@ -51,7 +51,7 @@ Function New-MicrosoftEntraIdApp Update-AzADApplication -ApplicationId $AppId -IdentifierUri "api://$AppId" -ErrorVariable ErrorUpdate if ($ErrorUpdate) { - Write-Error "Failed to add App Id Uri" + Write-Error "Failed to add App Id Uri. Error: $ErrorUpdate" return $Return } @@ -59,40 +59,40 @@ Function New-MicrosoftEntraIdApp $ScopeId = [guid]::NewGuid().ToString() $ScopeName = "user_impersonation" $Api = @{ - Oauth2PermissionScopes = @( + oauth2PermissionScopes = @( @{ - AdminConsentDescription = "Sign in to access $Name WinGet rest source" - AdminConsentDisplayName = "Access WinGet rest source" - UserConsentDescription = "Sign in to access $Name WinGet rest source" - UserConsentDisplayName = "Access WinGet rest source" - Id = $ScopeId - IsEnabled = $true - Type = "User" - Value = $ScopeName + adminConsentDescription = "Sign in to access $Name WinGet rest source" + adminConsentDisplayName = "Access WinGet rest source" + userConsentDescription = "Sign in to access $Name WinGet rest source" + userConsentDisplayName = "Access WinGet rest source" + id = $ScopeId + isEnabled = $true + type = "User" + value = $ScopeName } ) } Update-AzADApplication -ApplicationId $AppId -Api $Api -ErrorVariable ErrorUpdate if ($ErrorUpdate) { - Write-Error "Failed to add Api scope" + Write-Error "Failed to add Api scope. Error: $ErrorUpdate" return $Return } ## Add authorized client $Api = @{ - PreAuthorizedApplications = @( + preAuthorizedApplications = @( @{ - AppId = "7b8ea11a-7f45-4b3a-ab51-794d5863af15" - DelegatedPermissionIds = @($ScopeId) + appId = "7b8ea11a-7f45-4b3a-ab51-794d5863af15" + delegatedPermissionIds = @($ScopeId) } @{ - AppId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" - DelegatedPermissionIds = @($ScopeId) + appId = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" + delegatedPermissionIds = @($ScopeId) } @{ - AppId = "1950a258-227b-4e31-a9cf-717495945fc2" - DelegatedPermissionIds = @($ScopeId) + appId = "1950a258-227b-4e31-a9cf-717495945fc2" + delegatedPermissionIds = @($ScopeId) } ) } diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index 98399dff..4aedb327 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -23,7 +23,7 @@ Function New-WinGetSource [Optional] The Azure location where objects will be created in. (Default: westus) .PARAMETER TemplateFolderPath - [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplate) + [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplates) .PARAMETER ParameterOutputPath [Optional] The directory where Parameter objects will be created in. (Default: Current Directory\Parameters) @@ -81,7 +81,7 @@ Function New-WinGetSource [Parameter(Position=1, Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "", [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", - [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplate", + [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", [Parameter(Position=7, Mandatory=$false)] [string]$PublisherName = "", @@ -95,11 +95,11 @@ Function New-WinGetSource [Parameter(Position=12,Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", [Parameter()] [switch]$ShowConnectionInstructions ) - + if($ImplementationPerformance -eq "Developer") { Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" } - + ############################### ## Check input paths if(!$(Test-Path -Path $TemplateFolderPath)) { @@ -110,10 +110,10 @@ Function New-WinGetSource Write-Error "REST Source Function Code is missing in specified path ($RestSourcePath)" return $false } - + ############################### ## Check Microsoft Entra Id input - if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !CreateNewMicrosoftEntraIdAppRegistration -and !MicrosoftEntraIdResource) { + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !$CreateNewMicrosoftEntraIdAppRegistration -and !$MicrosoftEntraIdResource) { Write-Error "When Microsoft Entra Id authentication is requested, either CreateNewMicrosoftEntraIdAppRegistration should be requested or MicrosoftEntraIdResource should be provided." return $false } @@ -147,14 +147,14 @@ Function New-WinGetSource return $false } else { - $MicrosoftEntraIdResource = !$Result.Resource - $MicrosoftEntraIdResourceScope = !$Result.ResourceScope + $MicrosoftEntraIdResource = $Result.Resource + $MicrosoftEntraIdResourceScope = $Result.ResourceScope } } ############################### ## Creates the ARM files - $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance + $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance -PublisherName $PublisherName -PublisherEmail $PublisherEmail -RestSourceAuthentication $RestSourceAuthentication -MicrosoftEntraIdResource $MicrosoftEntraIdResource -MicrosoftEntraIdResourceScope $MicrosoftEntraIdResourceScope if (!$ARMObjects) { Write-Error "Failed to create ARM parameter objects." return $false @@ -194,16 +194,16 @@ Function New-WinGetSource ## Shows how to connect local Windows Package Manager Client to newly created REST source if($ShowConnectionInstructions) { $ApiManagementName = $ARMObjects.Where({$_.ObjectType -eq "ApiManagement"}).Parameters.Parameters.serviceName.value - $ApiManagementURL = $(Get-AzApiManagement -Name $ApiManagementName -ResourceGroupName $ResourceGroup).RuntimeUrl + $ApiManagementURL = (Get-AzApiManagement -Name $ApiManagementName -ResourceGroupName $ResourceGroup).RuntimeUrl ## Post script Run Informational: #### Instructions on how to add the REST source to your Windows Package Manager Client Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" -InformationAction Continue - Write-Information -MessageData " winget source add -n ""restsource"" -a ""https://$ApiManagementURL/api/"" -t ""Microsoft.Rest""" -InformationAction Continue + Write-Information -MessageData " winget source add -n ""restsource"" -a ""$ApiManagementURL/winget/"" -t ""Microsoft.Rest""" -InformationAction Continue #### For more information about how to use the solution, visit the aka.ms link. Write-Information -MessageData "`nFor more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" -InformationAction Continue } - + return $true } diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index c652dac6..085f6d7c 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -55,14 +55,6 @@ Function Remove-WinGetManifest if(!$ResourceGroupName) { Write-Error "Failed to confirm Azure Function exists in Azure. Please verify and try again. Function Name: $FunctionName" -ErrorAction Stop } - - ############################### - ## Verify Azure Resources Exist - Write-Verbose -Message "Verifying that the Azure Resource $FunctionName exists.." - $Result = Test-AzureResource -ResourceName $FunctionName -ResourceGroup $ResourceGroupName - if(!$Result) { - Write-Error "Failed to confirm resources exist in Azure. Please verify and try again." -ErrorAction Stop - } ############################### ## REST api call @@ -73,7 +65,7 @@ Function Remove-WinGetManifest $ApiMethod = "Delete" - $FunctionApp = Get-AzWebApp -ResourceGroupName $ResourceGroupName -Name $FunctionName + $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName -Name $FunctionName ## can function key be part of the header $FunctionAppId = $FunctionApp.Id diff --git a/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 b/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 index 8a2cef2b..08f5cbcb 100644 --- a/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ARMTemplates.ps1 @@ -37,7 +37,7 @@ Function Test-ARMTemplates foreach($Object in $ARMObjects) { ## Validates that each ARM object will work. - Write-Information " Validation testing on ARM Resource ($($Object.ObjectType))." + Write-Information "Validation testing on ARM Resource ($($Object.ObjectType))." $AzResourceResult = Test-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -Mode Complete -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath ## If the ARM object fails validation, report error to screen. @@ -47,7 +47,7 @@ Function Test-ARMTemplates TestResult = $AzResourceResult } - Write-Error -Message "Failed to validate ARM Template with Template parameters. Template: $Object.TemplatePath, Parameters: $Object.ParameterPath" -TargetObject $ErrReturnObject + Write-Error -Message "Failed to validate ARM Template with Template parameters. Template: $($Object.TemplatePath), Parameters: $($Object.ParameterPath)" -TargetObject $ErrReturnObject $Return += $ErrReturnObject } diff --git a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 b/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 deleted file mode 100644 index 80d11553..00000000 --- a/Tools/PowershellModule/src/Library/Test-AzureResource.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Function Test-AzureResource -{ - <# - .SYNOPSIS - Returns a boolean result validating that the Resource Group and Resource exist. - - .DESCRIPTION - Returns a boolean result validating that the Resource Group and Resource exist. - - .PARAMETER ResourceGroup - The Resource Group that the objects will be tested in reference to. - - .PARAMETER ResourceName - Name of the Azure Resource. - - .PARAMETER ResourceType - Type of the Azure Resource. - - .EXAMPLE - Test-AzureResource -ResourceGroup "WinGet" -ResourceName "contosorestsource" - - Returns a boolean result validating that the Resource Group and Resource exist. - - #> - PARAM( - [Parameter(Position=0, Mandatory = $true)] [string]$ResourceGroup, - [Parameter(Position=1, Mandatory = $true)] [string]$ResourceName, - [ValidateSet("Function")] - [Parameter(Position=2, Mandatory = $false)] [string]$ResourceType = "Function" - ) - - $Result = $false - $AzureResourceGroupName = $ResourceGroup - $AzureResourceName = $ResourceName - - $AzureResourceGroupNameNullOrWhiteSpace = $false - $AzureResourceGroupNameExists = $false - - $AzureResourceNameNullOrWhiteSpace = $false - $AzureResourceNameExists = $false - - ##Determines if the Azure Resource Group Name is not null or empty - if([string]::IsNullOrWhiteSpace($AzureResourceGroupName)) { - $AzureResourceGroupName = "" - $AzureResourceGroupNameNullOrWhiteSpace = $true - } - - if($(Get-AzResourceGroup).Where({$_.ResourceGroupName -eq $AzureResourceGroupName}).Count -gt 0) { - $AzureResourceGroupNameExists = $true - } - - ## Determines if the Azure Resource Name is not null or empty - if([string]::IsNullOrWhiteSpace($AzureResourceName)) { - $AzureResourceName = "" - $AzureResourceNameNullOrWhiteSpace = $true - } - - ## Determines if the Azure Resource Name is in Azure - if ($AzureResourceGroupNameExists) { - switch ($ResourceType) { - "Function" { - if($(Get-AzFunctionApp -ResourceGroupName $AzureResourceGroupName).Where({$_.Name -eq $AzureResourceName}).Count -gt 0) { - $AzureResourceNameExists = $true - } - } - } - } - - $VerboseMessage = "Azure Resources:`n" - $VerboseMessage += " Azure Resource Group Exists: $AzureResourceGroupNameExists`n" - $VerboseMessage += " Azure Resource Exists: $AzureResourceNameExists" - Write-Verbose -Message $VerboseMessage - - ## If either the Azure Resource Name or the Azure Resource Group Name are null, error. - if($AzureResourceGroupNameNullOrWhiteSpace -or $AzureResourceNameNullOrWhiteSpace -or !$AzureResourceGroupNameExists -or !$AzureResourceNameExists) { - $ErrorMessage = "Both the Azure Resource Group and Resource Names can not be null and must exist. Please verify that the Azure Resource Group and Resource exist." - $ErrReturnObject = @{ - AzureResourceGroupNameNullOrWhiteSpace = $AzureResourceGroupNameNullOrWhiteSpace - AzureResourceNameNullOrWhiteSpace = $AzureResourceNameNullOrWhiteSpace - AzureResourceGroupNameExists = $AzureResourceGroupNameExists - AzureResourceNameExists = $AzureResourceNameExists - Result = $false - } - - Write-Error -Message $ErrorMessage -TargetObject $ErrReturnObject - } - - $Result = $AzureResourceGroupNameExists -and $AzureResourceNameExists - - return $Result -} \ No newline at end of file diff --git a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 index 90ed7842..b75d71f6 100644 --- a/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Test-WinGetManifest.ps1 @@ -40,11 +40,11 @@ Function Test-WinGetManifest ## Convert to full path if applicable $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) - $PathFound = Test-Path -Path $Path; + $PathFound = Test-Path -Path $Path if ($PathFound) { ## Construct $Manifest from path then validate - $Return = $true; + $Return = $true } else { @@ -53,7 +53,7 @@ Function Test-WinGetManifest } "Object" { ## Validate manifest - $Return = $true; + $Return = $true } } diff --git a/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json b/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json index 53066f42..2e1c8db3 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/AppConfig/appconfig.json @@ -28,6 +28,13 @@ "description": "List of feature flags to set. These are specifically for the feature management tools in App Config, not key-vaule pairs." } }, + "deployAppConfigValues": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Specifies whether to deploy App Config values." + } + }, "sku": { "type": "string", "defaultValue": "Standard", @@ -50,6 +57,7 @@ } }, { + "condition": "[parameters('deployAppConfigValues')]", "apiVersion": "2021-10-01-preview", "name": "[format('{0}/{1}', parameters('appConfigName'), concat('.appconfig.featureflag~2F', parameters('featureFlags')[copyIndex()].id))]", "type": "Microsoft.AppConfiguration/configurationStores/keyValues", @@ -61,7 +69,7 @@ "count": "[length(parameters('featureFlags'))]" }, "properties": { - "value": "[concat('{\"id\":\"',parameters('featureFlags')[copyIndex()].id,'\",\"description\":\"',parameters('featureFlags')[copyIndex()].id,'\",\"enabled\":',parameters('featureFlags')[copyIndex()].enabled,',\"conditions\":{\"client_filters\":[]}}')]", + "value": "[concat('{\"id\":\"',parameters('featureFlags')[copyIndex()].id,'\",\"description\":\"',parameters('featureFlags')[copyIndex()].description,'\",\"enabled\":',parameters('featureFlags')[copyIndex()].enabled,',\"conditions\":{\"client_filters\":[]}}')]", "contentType": "application/vnd.microsoft.appconfig.ff+json;charset=utf-8" } } diff --git a/src/WinGet.RestSource.Infrastructure/Templates/AzureFunction/azurefunction.json b/src/WinGet.RestSource.Infrastructure/Templates/AzureFunction/azurefunction.json index 1c385eee..8214c90c 100644 --- a/src/WinGet.RestSource.Infrastructure/Templates/AzureFunction/azurefunction.json +++ b/src/WinGet.RestSource.Infrastructure/Templates/AzureFunction/azurefunction.json @@ -218,12 +218,6 @@ "alwaysOn": "[parameters('enableAlwaysOn')]", "minTlsVersion": "[parameters('minimumTlsVersion')]" }, - "appSettings": [ - { - "name": "FUNCTIONS_EXTENSION_VERSION", - "value": "[parameters('functionExtensionVersion')]" - } - ], "httpsOnly": true }, "resources": [ @@ -232,8 +226,7 @@ "type": "config", "name": "appsettings", "dependsOn": [ - "[concat('Microsoft.Web/Sites/', parameters('functionName'))]", - "AzureFunctionKeyVaultAccessPolicy" + "[concat('Microsoft.Web/Sites/', parameters('functionName'))]" ], "properties": { "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(variables('appInsightResourceId'), '2015-05-01').InstrumentationKey]", @@ -246,6 +239,7 @@ "FunctionName": "[parameters('functionName')]", "FUNCTIONS_EXTENSION_VERSION": "[parameters('functionExtensionVersion')]", "FUNCTIONS_WORKER_RUNTIME": "dotnet", + "FUNCTIONS_INPROC_NET8_ENABLED": 1, "ManifestCacheEndpoint": "[parameters('manifestCacheEndpoint')]", "ServerIdentifier": "[parameters('serverIdentifier')]", "WEBSITE_FIRST_PARTY_ID": "AntMDS", @@ -273,50 +267,6 @@ } } ] - }, - { - "apiVersion": "2019-05-01", - "name": "AzureFunctionKeyVaultAccessPolicy", - "type": "Microsoft.Resources/deployments", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', parameters('functionName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "name": "[concat(parameters('keyVaultName'), '/add')]", - "apiVersion": "2016-10-01", - "properties": { - "accessPolicies": [ - { - "tenantId": "[reference(concat('Microsoft.Web/sites/', parameters('functionName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').tenantId]", - "objectId": "[reference(concat('Microsoft.Web/sites/', parameters('functionName'), '/providers/Microsoft.ManagedIdentity/Identities/default'), '2015-08-31-PREVIEW').principalId]", - "permissions": { - "keys": [ - "Get" - ], - "secrets": [ - "Get" - ], - "certificates": [ - "Get" - ] - }, - "metadata": { - "description": "This is the service principal for the Service Functions Azure Function." - } - } - ] - } - } - ] - } - } } ], "outputs": {} From d7059b4a20d68b954f59b56d886c133b89ae300c Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:13:08 -0800 Subject: [PATCH 14/16] Fix e2e after refactoring --- .../src/Library/Add-WinGetManifest.ps1 | 11 +- .../src/Library/Get-WinGetManifest.ps1 | 12 +- .../src/Library/New-ARMObjects.ps1 | 38 ++-- .../src/Library/New-ARMParameterObjects.ps1 | 11 +- .../src/Library/Update-WinGetSource.ps1 | 209 ++++++++++++++++++ .../src/Library/WinGetManifest.ps1 | 14 +- .../src/Microsoft.WinGet.Source.psm1 | 6 +- .../YamlToRestConverter.cs | 8 +- src/WinGet.RestSource.sln | 3 +- .../Cosmos/CosmosDataStore.cs | 28 ++- 10 files changed, 296 insertions(+), 44 deletions(-) create mode 100644 Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index a6f15c0b..a9fa6a63 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -107,11 +107,11 @@ Function Add-WinGetManifest Write-Verbose -Message "Contents of manifest have been retrieved. Package Identifier: $($Manifest.PackageIdentifier)." Write-Verbose -Message "Confirming that the Package ID doesn't already exist in Azure for $($Manifest.PackageIdentifier)." - $ApiHeader.Add["x-functions-key"] = $FunctionKeyGet + $ApiHeader["x-functions-key"] = $FunctionKeyGet $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier $Response = Invoke-RestMethod $AzFunctionURL -Headers $ApiHeader -Method $ApiMethodGet -ErrorVariable ErrorInvoke - if ($ErrorInvoke) { + if ($ErrorInvoke -or $Response.Data.Count -eq 0) { ## No existing manifest retrieved, submit as new manifest Write-Verbose "No manifest that matched. Package Identifier: $($Manifest.PackageIdentifier)" @@ -123,7 +123,7 @@ Function Add-WinGetManifest ## Existing manifest retrieved, submit as update existing manifest Write-Verbose "Found manifest that matched. Package Identifier: $($Manifest.PackageIdentifier)" - if($Response.Data.Count -ne 1) { + if($Response.Data.Count -gt 1) { Write-Error "Found conflicting manifests. Package Identifier: $($Manifest.PackageIdentifier)" return } @@ -131,6 +131,11 @@ Function Add-WinGetManifest $ApiMethod = $ApiMethodPut $AzFunctionURL = $AzFunctionURLBase + $Manifest.PackageIdentifier $ApiHeader["x-functions-key"] = $FunctionKeyPut + + ## Merge with prior manifest + $PriorManifest = [WinGetManifest]::CreateFromObject($Response.Data[0]) + $ApplicationManifest = Get-WinGetManifest -Path $Path -PriorManifest $PriorManifest + $Manifest = $ApplicationManifest[0] } Write-Verbose -Message "The Manifest will be added using the $ApiMethod REST API." diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index 4211d089..cc82c133 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -18,9 +18,9 @@ Function Get-WinGetManifest If you are processing a multi-file manifest, point to the folder that contains all yamls. Note: all yamls within the folder must be part of the same package manifest. - .PARAMETER JSON - A JSON string containing a single application's REST source Packages Manifest that will be merged with locally processed .yaml files. This is - used by the script infrastructure internally and is not expected to be useful to an end user using this command. + .PARAMETER PriorManifest + A WinGetManifest object containing a single application's REST source Packages Manifest that will be merged with locally processed .yaml files. + This is used by the script infrastructure internally. .PARAMETER FunctionName Name of the Azure Function Name that contains the Windows Package Manager REST APIs. @@ -60,7 +60,7 @@ Function Get-WinGetManifest [CmdletBinding(DefaultParameterSetName = 'Azure')] PARAM( [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, - [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$JSON = $null, + [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$PriorManifest = $null, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Azure")] [string]$FunctionName, [Parameter(Position=1, Mandatory=$true, ParameterSetName="Azure", ValueFromPipeline=$true)][ValidateNotNullOrEmpty()] [string]$PackageIdentifier, [Parameter(Position=2, Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName = "" @@ -233,9 +233,9 @@ Function Get-WinGetManifest ".yaml" { ## Directory - *.yaml files included within. Write-Verbose -Message "YAML Files have been found in the target directory. Building a JSON manifest with found files." - if($Json){ + if($PriorManifest){ Write-Verbose "Prior manifest provided. New manifest will be merged with prior manifest." - $Return += [WinGetManifest]::CreateFromString([Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $JSON.GetJson())) + $Return += [WinGetManifest]::CreateFromString([Microsoft.WinGet.RestSource.PowershellSupport.YamlToRestConverter]::AddManifestToPackageManifest($Path, $PriorManifest.GetJson())) } else{ Write-Verbose "Prior manifest not provided." diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 1057a5af..d6371d27 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -70,16 +70,16 @@ Function New-ARMObjects $AzureFunctionHostKeyName = "AzureFunctionHostKey" $AppConfigPrimaryEndpointName = "AppConfigPrimaryEndpoint" $AppConfigSecondaryEndpointName = "AppConfigSecondaryEndpoint" - + $FunctionAppUrls = @() - + ## Creates the Azure Resources following the ARM template / parameters Write-Information "Creating Azure Resources following ARM Templates." - + ## This is order specific, please ensure you used the New-ARMParameterObjects function to create this object in the pre-determined order. foreach ($Object in $ARMObjects) { Write-Information "Creating the Azure Object - $($Object.ObjectType)" - + ## If the object to be created is an Azure Function, then complete these pre-required steps before creating the Azure Function. if ($Object.ObjectType -eq "Function") { $CosmosAccountEndpointValue = $(Get-AzCosmosDBAccount -Name $CosmosAccountName -ResourceGroupName $ResourceGroup).DocumentEndpoint | ConvertTo-SecureString -AsPlainText -Force @@ -105,17 +105,19 @@ Function New-ARMObjects } } - ## Update backend urls and re-create parameters file - $ApiManagementParameters.backendUrls.value = $FunctionAppUrls - Write-Verbose -Message "Re-creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" - $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 - $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + ## Update backend urls and re-create parameters file if needed + if (!$ApiManagementParameters.backendUrls.value) { + $ApiManagementParameters.backendUrls.value = $FunctionAppUrls + Write-Verbose -Message "Re-creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" + $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 + $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + } ## Set secret get for Api management service Write-Verbose "Set keyvault secret access for Api Management service" Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $ApiManagement.Identity.PrincipalId -PermissionsToSecrets Get } - + ## Creates the Azure Resource $DeployResult = New-AzResourceGroupDeployment -ResourceGroupName $ResourceGroup -TemplateFile $Object.TemplatePath -TemplateParameterFile $Object.ParameterPath -Mode Incremental -ErrorVariable DeployError @@ -139,7 +141,7 @@ Function New-ARMObjects $FunctionAppId = $FunctionApp.Id $FunctionAppUrls += "https://$($FunctionApp.DefaultHostName)/api" - + ## Assign necessary Azure roles Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Account Contributor" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Blob Data Owner" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" @@ -149,6 +151,10 @@ Function New-ARMObjects Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "Storage Queue Data Message Sender" -ResourceGroup $ResourceGroup -ResourceName $StorageAccountName -ResourceType "Microsoft.Storage/storageAccounts" Set-RoleAssignment -PrincipalId $FunctionApp.IdentityPrincipalId -RoleName "App Configuration Data Reader" -ResourceGroup $ResourceGroup -ResourceName $AppConfigName -ResourceType "Microsoft.AppConfiguration/configurationStores" + ## Set keyvault secrets get permission + Write-Verbose "Set keyvault secret access for Azure Function" + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $FunctionApp.IdentityPrincipalId -PermissionsToSecrets Get + ## Assign cosmos db roles $CosmosAccount = Get-AzCosmosDBAccount -ResourceGroupName $ResourceGroup -Name $CosmosAccountName $RoleId = "00000000-0000-0000-0000-000000000002" # Built in contributor role @@ -157,7 +163,7 @@ Function New-ARMObjects Write-Verbose "Assigning Cosmos DB Account contributor role" New-AzCosmosDBSqlRoleAssignment -AccountName $CosmosAccountName -ResourceGroupName $ResourceGroup -RoleDefinitionId $RoleId -Scope "/" -PrincipalId $FunctionApp.IdentityPrincipalId } - + ## Create Function app key and also add to keyvault $NewFunctionKeyValue = New-FunctionAppKey $Result = Invoke-AzRestMethod -Path "$FunctionAppId/host/default/functionKeys/WinGetRestSourceAccess?api-version=2024-04-01" -Method PUT -Payload (@{properties=@{value = $NewFunctionKeyValue}} | ConvertTo-Json -Depth 8) @@ -165,13 +171,13 @@ Function New-ARMObjects Write-Error "Failed to create Azure Function key. $($Result.Content)" return $false } - + Write-Information -MessageData "Add Function App host key to keyvault." Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $AzureFunctionHostKeyName -SecretValue ($NewFunctionKeyValue | ConvertTo-SecureString -AsPlainText -Force) Write-Information -MessageData "Publishing function files to the Azure Function." $DeployResult = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $FunctionName -Force -ErrorVariable DeployError - + ## Verifies that no error occured when publishing the Function App if ($DeployError -or !$DeployResult) { $ErrReturnObject = @{ @@ -182,7 +188,7 @@ Function New-ARMObjects Write-Error "Failed to publishing the Function App. $DeployError" -TargetObject $ErrReturnObject return $false } - + ## Restart the Function App Restart-AzFunctionApp -Name $FunctionName -ResourceGroupName $ResourceGroup -Force } @@ -221,6 +227,6 @@ Function New-ARMObjects } } } - + return $true } diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 index 48f33028..c54a2b88 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 @@ -41,6 +41,9 @@ Function New-ARMParameterObjects .PARAMETER MicrosoftEntraIdResourceScope [Optional] Microsoft Entra Id authentication resource scope + .PARAMETER ForUpdate + [Optional] The operation is for update. (Default: False) + .EXAMPLE New-ARMParameterObjects -ParameterFolderPath "C:\WinGet\Parameters" -TemplateFolderPath "C:\WinGet\Templates" -Name "contosorestsource" -AzLocation "westus" -ImplementationPerformance "Developer" @@ -59,6 +62,7 @@ Function New-ARMParameterObjects [Parameter(Position=7, Mandatory=$false)] [string]$RestSourceAuthentication = "None", [Parameter(Position=8, Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "" + [Parameter()] [switch]$ForUpdate ) $ARMObjects = @() @@ -493,7 +497,12 @@ Function New-ARMParameterObjects ## Converts the structure of the variable to a JSON file. Write-Verbose -Message "Creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 - $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + if ($ForUpdate) { + $ParameterFile | Out-File -FilePath $Object.ParameterPath -NoClobber -ErrorAction SilentlyContinue + } + else { + $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + } } ## Returns the completed object. diff --git a/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 new file mode 100644 index 00000000..4aedb327 --- /dev/null +++ b/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 @@ -0,0 +1,209 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +Function New-WinGetSource +{ + <# + .SYNOPSIS + Creates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. + + .DESCRIPTION + Creates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. + + .PARAMETER Name + The name of the objects that will be created + + .PARAMETER ResourceGroup + [Optional] The name of the Resource Group that the Windows Package Manager REST source will reside. All Azure + resources will be created in in this Resource Group (Default: WinGetRestsource) + + .PARAMETER SubscriptionName + [Optional] The name of the subscription that will be used to host the Windows Package Manager REST source. (Default: Current connected subscription) + + .PARAMETER Region + [Optional] The Azure location where objects will be created in. (Default: westus) + + .PARAMETER TemplateFolderPath + [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplates) + + .PARAMETER ParameterOutputPath + [Optional] The directory where Parameter objects will be created in. (Default: Current Directory\Parameters) + + .PARAMETER RestSourcePath + [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip) + + .PARAMETER PublisherName + [Optional] The WinGet rest source publisher name + + .PARAMETER PublisherEmail + [Optional] The WinGet rest source publisher email + + .PARAMETER ImplementationPerformance + [Optional] ["Developer", "Basic", "Enhanced"] specifies the performance of the resources to be created for the Windows Package Manager REST source. + | Preference | Description | + |------------|-------------------------------------------------------------------------------------------------------------------------| + | Developer | Specifies lowest cost for developing the Windows Package Manager REST source. Uses free-tier options when available. | + | Basic | Specifies a basic functioning Windows Package Manager REST source. | + | Enhanced | Specifies a higher tier functionality with data replication across multiple data centers. | + + (Default: Basic) + + .PARAMETER RestSourceAuthentication + [Optional] ["None", "MicrosoftEntraId"] The WinGet rest source authentication type. [Default: None] + + .PARAMETER CreateNewMicrosoftEntraIdAppRegistration + [Optional] If specified, a new Microsoft Entra Id app registration will be created. (Default: False) + + .PARAMETER MicrosoftEntraIdResource + [Optional] Microsoft Entra Id authentication resource + + .PARAMETER MicrosoftEntraIdResourceScope + [Optional] Microsoft Entra Id authentication resource scope + + .PARAMETER ShowConnectionInstructions + [Optional] If specified, the instructions for connecting to the Windows Package Manager REST source. (Default: False) + + .EXAMPLE + New-WinGetSource -Name "contosorestsource" + + Creates the Windows Package Manager REST source in Azure with resources named "contosorestsource" in the westus region of + Azure with the basic level performance. + + .EXAMPLE + New-WinGetSource -Name "contosorestsource" -ResourceGroup "WinGet" -SubscriptionName "Visual Studio Subscription" -Region "westus" -ParameterOutput "C:\WinGet" -ImplementationPerformance "Basic" -ShowConnectionInformation + + Creates the Windows Package Manager REST source in Azure with resources named "contosorestsource" in the westus region of + Azure with the basic level performance in the "Visual Studio Subscription" Subscription. Displays the required command + to connect the WinGet client to the new REST source after the REST source has been created. + + #> + PARAM( + [Parameter(Position=0, Mandatory=$true)] [string]$Name, + [Parameter(Position=1, Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", + [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", + [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", + [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", + [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", + [Parameter(Position=7, Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Position=8, Mandatory=$false)] [string]$PublisherEmail = "", + [ValidateSet("Developer", "Basic", "Enhanced")] + [Parameter(Position=9, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [ValidateSet("None", "MicrosoftEntraId")] + [Parameter(Position=10,Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter()] [switch]$CreateNewMicrosoftEntraIdAppRegistration, + [Parameter(Position=11,Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Position=12,Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", + [Parameter()] [switch]$ShowConnectionInstructions + ) + + if($ImplementationPerformance -eq "Developer") { + Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" + } + + ############################### + ## Check input paths + if(!$(Test-Path -Path $TemplateFolderPath)) { + Write-Error "REST Source Function Code is missing in specified path ($TemplateFolderPath)" + return $false + } + if(!$(Test-Path -Path $RestSourcePath)) { + Write-Error "REST Source Function Code is missing in specified path ($RestSourcePath)" + return $false + } + + ############################### + ## Check Microsoft Entra Id input + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !$CreateNewMicrosoftEntraIdAppRegistration -and !$MicrosoftEntraIdResource) { + Write-Error "When Microsoft Entra Id authentication is requested, either CreateNewMicrosoftEntraIdAppRegistration should be requested or MicrosoftEntraIdResource should be provided." + return $false + } + + ############################### + ## Create folder for the Parameter output path + $Result = New-Item -ItemType Directory -Path $ParameterOutputPath -Force + if($Result) { + Write-Verbose -Message "Created Directory to contain the ARM Parameter files ($($Result.FullName))." + } + else { + Write-Error "Failed to create ARM parameter files output path. Path: $ParameterOutputPath" + return $false + } + + ############################### + ## Connects to Azure, if not already connected. + Write-Information "Testing connection to Azure." + $Result = Connect-ToAzure -SubscriptionName $SubscriptionName + if(!($Result)) { + Write-Error "Failed to connect to Azure. Please run Connect-AzAccount to connect to Azure, or re-run the cmdlet and enter your credentials." + return $false + } + + ############################### + ## Create new Microsoft Entra Id app registration if requested + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and $CreateNewMicrosoftEntraIdAppRegistration) { + $Result = New-MicrosoftEntraIdApp -Name $Name + if (!$Result.Result) { + Write-Error "Failed to create new Microsoft Entra Id app registration." + return $false + } + else { + $MicrosoftEntraIdResource = $Result.Resource + $MicrosoftEntraIdResourceScope = $Result.ResourceScope + } + } + + ############################### + ## Creates the ARM files + $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance -PublisherName $PublisherName -PublisherEmail $PublisherEmail -RestSourceAuthentication $RestSourceAuthentication -MicrosoftEntraIdResource $MicrosoftEntraIdResource -MicrosoftEntraIdResourceScope $MicrosoftEntraIdResourceScope + if (!$ARMObjects) { + Write-Error "Failed to create ARM parameter objects." + return $false + } + + ############################### + ## Create Resource Group + Write-Information "Creating the Resource Group used to host the Windows Package Manager REST source. Name: $ResourceGroup, Region: $Region" + $Result = Add-AzureResourceGroup -Name $ResourceGroup -Region $Region + if (!$Result) { + Write-Error "Failed to create Azure resource group. Name: $ResourceGroup Region: $Region" + return $false + } + + #### Verifies ARM Parameters are correct + $Result = Test-ARMTemplates -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup + if($Result){ + $ErrReturnObject = @{ + ARMObjects = $ARMObjects + ResourceGroup = $ResourceGroup + Result = $Result + } + + Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject + return $false + } + + ############################### + ## Creates Azure Objects with ARM Templates and Parameters + $Result = New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup + if (!$Result) { + Write-Error "Failed to create Azure resources for WinGet rest source" + return $false + } + + ############################### + ## Shows how to connect local Windows Package Manager Client to newly created REST source + if($ShowConnectionInstructions) { + $ApiManagementName = $ARMObjects.Where({$_.ObjectType -eq "ApiManagement"}).Parameters.Parameters.serviceName.value + $ApiManagementURL = (Get-AzApiManagement -Name $ApiManagementName -ResourceGroupName $ResourceGroup).RuntimeUrl + + ## Post script Run Informational: + #### Instructions on how to add the REST source to your Windows Package Manager Client + Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" -InformationAction Continue + Write-Information -MessageData " winget source add -n ""restsource"" -a ""$ApiManagementURL/winget/"" -t ""Microsoft.Rest""" -InformationAction Continue + + #### For more information about how to use the solution, visit the aka.ms link. + Write-Information -MessageData "`nFor more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" -InformationAction Continue + } + + return $true +} diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 index cd5d45ea..25530ea6 100644 --- a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -146,7 +146,7 @@ class WinGetInstallationMetadata { [string]$DefaultInstallLocation [WinGetInstallationMetadataFile[]]$Files - + WinGetInstallationMetadata () {} } @@ -211,9 +211,9 @@ class WinGetManifest { [string] $PackageIdentifier [WinGetVersion[]] $Versions - + WinGetManifest () {} - + [string] GetJson () { ## Not using ConvertTo-Json here since we want more control on null property handling @@ -226,19 +226,19 @@ class WinGetManifest return [System.Text.Json.JsonSerializer]::Serialize($this, $options) } - + static [WinGetManifest] CreateFromString ([string] $a) { - Write-Verbose -Message "Creating a WinGetManifest object from String object" + Write-Verbose -Message "Creating a WinGetManifest object from String." $options = [System.Text.Json.JsonSerializerOptions]::new() return [System.Text.Json.JsonSerializer]::Deserialize($a, [WinGetManifest], $options) } - + static [WinGetManifest] CreateFromObject ([psobject] $a) { - Write-Verbose -Message "Creating a WinGetManifest object from PsObject object" + Write-Verbose -Message "Creating a WinGetManifest object from PsObject object." $json = ConvertTo-Json $a -Depth 16 -Compress diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 index d76293b1..33e22ee1 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psm1 @@ -10,13 +10,13 @@ try { switch ($architecture) { "X64" { - Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force -ErrorAction SilentlyContinue } "X86" { - Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x86\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-x86\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force -ErrorAction SilentlyContinue } "Arm64" { - Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-arm64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force + Copy-Item -Path "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\runtimes\win-arm64\native\WinGetUtil.dll" -Destination "$PSScriptRoot\Library\WinGet.RestSource.PowershellSupport\WinGetUtil.dll" -Force -ErrorAction SilentlyContinue } Default { throw "Powershell Core runtime architecture not supported" diff --git a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs index c7a7e258..4bfe8f03 100644 --- a/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs +++ b/src/WinGet.RestSource.PowershellSupport/YamlToRestConverter.cs @@ -74,7 +74,13 @@ public static string AddManifestToPackageManifest( manifest, string.IsNullOrWhiteSpace(priorRestManifest) ? null : Parser.StringParser(priorRestManifest)); - return JsonConvert.SerializeObject(packageManifest); + return JsonConvert.SerializeObject( + packageManifest, + Newtonsoft.Json.Formatting.None, + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + }); } } } diff --git a/src/WinGet.RestSource.sln b/src/WinGet.RestSource.sln index 61fa72c0..a04b461e 100644 --- a/src/WinGet.RestSource.sln +++ b/src/WinGet.RestSource.sln @@ -80,13 +80,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{5DF7 ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Get-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMObjects.ps1 ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 = ..\Tools\PowershellModule\src\Library\New-ARMParameterObject.ps1 + ..\Tools\PowershellModule\src\Library\New-MicrosoftEntraIdApp.ps1 = ..\Tools\PowershellModule\src\Library\New-MicrosoftEntraIdApp.ps1 ..\Tools\PowershellModule\src\Library\New-WinGetSource.ps1 = ..\Tools\PowershellModule\src\Library\New-WinGetSource.ps1 ..\Tools\PowershellModule\src\Library\Remove-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Remove-WinGetManifest.ps1 ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 = ..\Tools\PowershellModule\src\Library\Test-ARMTemplate.ps1 - ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 = ..\Tools\PowershellModule\src\Library\Test-AzureResource.ps1 ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 = ..\Tools\PowershellModule\src\Library\Test-ConnectionToAzure.ps1 ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExist.ps1 = ..\Tools\PowershellModule\src\Library\Test-PowerShellModuleExist.ps1 ..\Tools\PowershellModule\src\Library\Test-WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\Test-WinGetManifest.ps1 + ..\Tools\PowershellModule\src\Library\Update-WinGetSource.ps1 = ..\Tools\PowershellModule\src\Library\Update-WinGetSource.ps1 ..\Tools\PowershellModule\src\Library\WinGetManifest.ps1 = ..\Tools\PowershellModule\src\Library\WinGetManifest.ps1 EndProjectSection EndProject diff --git a/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs b/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs index c0a93438..d5ec9e5b 100644 --- a/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs +++ b/src/WinGet.RestSource/Cosmos/CosmosDataStore.cs @@ -213,9 +213,17 @@ public async Task DeleteVersion(string packageIdentifier, string packageVersion) // Remove Version cosmosDocument.Document.RemoveVersion(packageVersion); - // Save Package - ApiDataValidator.Validate(cosmosDocument.Document); - await this.cosmosDatabase.Update(cosmosDocument); + if (cosmosDocument.Document.Versions is null) + { + // No version left, delete the package + await this.DeletePackage(packageIdentifier); + } + else + { + // Save Package + ApiDataValidator.Validate(cosmosDocument.Document); + await this.cosmosDatabase.Update(cosmosDocument); + } } /// @@ -272,9 +280,17 @@ public async Task DeleteInstaller(string packageIdentifier, string packageVersio // Remove Installer cosmosDocument.Document.RemoveInstaller(installerIdentifier, packageVersion); - // Save Document - ApiDataValidator.Validate(cosmosDocument.Document); - await this.cosmosDatabase.Update(cosmosDocument); + if (cosmosDocument.Document.GetVersion(packageVersion)[0].Installers is null) + { + // No installer left, delete the version + await this.DeleteVersion(packageIdentifier, packageVersion); + } + else + { + // Save Document + ApiDataValidator.Validate(cosmosDocument.Document); + await this.cosmosDatabase.Update(cosmosDocument); + } } /// From 916a9c3c25c9cc880ceec2e9c8b1129d6309a871 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 6 Dec 2024 06:12:21 -0800 Subject: [PATCH 15/16] Update-WinGetSource --- .../src/Library/Add-AzureResourceGroup.ps1 | 2 +- .../src/Library/Add-WinGetManifest.ps1 | 4 +- .../src/Library/Connect-ToAzure.ps1 | 4 +- .../src/Library/Find-WinGetManifest.ps1 | 8 +- .../src/Library/Get-WinGetManifest.ps1 | 4 +- .../src/Library/New-ARMObjects.ps1 | 1 + .../src/Library/New-ARMParameterObjects.ps1 | 23 ++- .../src/Library/New-WinGetSource.ps1 | 30 +-- .../src/Library/Remove-WinGetManifest.ps1 | 4 +- .../src/Library/Test-ConnectionToAzure.ps1 | 4 +- .../src/Library/Update-WinGetSource.ps1 | 195 +++++++++--------- .../src/Library/WinGetManifest.ps1 | 12 +- .../src/Microsoft.WinGet.Source.psd1 | 2 +- 13 files changed, 146 insertions(+), 147 deletions(-) diff --git a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 index dd29c27f..2572f7fe 100644 --- a/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 +++ b/Tools/PowershellModule/src/Library/Add-AzureResourceGroup.ps1 @@ -43,7 +43,7 @@ Function Add-AzureResourceGroup ## Determines if the Resource Group already exists Write-Verbose "Retrieving details from Azure for the Resource Group name $Name" - $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable ErrorGet -InformationAction SilentlyContinue -WarningAction SilentlyContinue + $Result = Get-AzResourceGroup -Name $Name -ErrorAction SilentlyContinue -ErrorVariable ErrorGet if(!$Result) { Write-Information "Failed to retrieve Resource Group, will attempt to create $Name in the specified $Region." diff --git a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 index a9fa6a63..b09e46b6 100644 --- a/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Add-WinGetManifest.ps1 @@ -44,7 +44,7 @@ Function Add-WinGetManifest PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, [Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$true)] [string]$Path, - [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "" + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "" ) BEGIN { @@ -94,6 +94,8 @@ Function Add-WinGetManifest } PROCESS { + $Path = [System.IO.Path]::GetFullPath($Path, $pwd.Path) + ############################### ## Gets the content from the Package Manifest (*.JSON, or *.YAML) file for posting to REST source. Write-Verbose -Message "Retrieving a copy of the app Manifest file for submission to WinGet source." diff --git a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 index 6325e7e1..d1429e4b 100644 --- a/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Connect-ToAzure.ps1 @@ -38,8 +38,8 @@ Function Connect-ToAzure #> PARAM( - [Parameter(Position=0, Mandatory=$false)] [string]$SubscriptionName = "", - [Parameter(Position=1, Mandatory=$false)] [string]$SubscriptionId = "" + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Mandatory=$false)] [string]$SubscriptionId = "" ) $TestAzureConnection = $false diff --git a/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 index baee1c68..9c782b77 100644 --- a/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Find-WinGetManifest.ps1 @@ -47,10 +47,10 @@ Function Find-WinGetManifest #> PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, - [Parameter(Position=1, Mandatory=$false)] [string]$Query = "", - [Parameter(Position=2, Mandatory=$false)] [string]$PackageIdentifier = "", - [Parameter(Position=3, Mandatory=$false)] [string]$PackageName = "", - [Parameter(Position=4, Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Mandatory=$false)] [string]$Query = "", + [Parameter(Mandatory=$false)] [string]$PackageIdentifier = "", + [Parameter(Mandatory=$false)] [string]$PackageName = "", + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "", [Parameter()] [switch]$Exact ) BEGIN diff --git a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 index cc82c133..054fd965 100644 --- a/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Get-WinGetManifest.ps1 @@ -60,10 +60,10 @@ Function Get-WinGetManifest [CmdletBinding(DefaultParameterSetName = 'Azure')] PARAM( [Parameter(Position=0, Mandatory=$true, ParameterSetName="File")] [string]$Path, - [Parameter(Position=1, Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$PriorManifest = $null, + [Parameter(Mandatory=$false,ParameterSetName="File")] [WinGetManifest]$PriorManifest = $null, [Parameter(Position=0, Mandatory=$true, ParameterSetName="Azure")] [string]$FunctionName, [Parameter(Position=1, Mandatory=$true, ParameterSetName="Azure", ValueFromPipeline=$true)][ValidateNotNullOrEmpty()] [string]$PackageIdentifier, - [Parameter(Position=2, Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName = "" + [Parameter(Mandatory=$false,ParameterSetName="Azure")] [string]$SubscriptionName = "" ) BEGIN { diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index d6371d27..59d3eee8 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -190,6 +190,7 @@ Function New-ARMObjects } ## Restart the Function App + Write-Verbose "Restarting Azure Function." Restart-AzFunctionApp -Name $FunctionName -ResourceGroupName $ResourceGroup -Force } elseif ($Object.ObjectType -eq "AppConfig") { diff --git a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 index c54a2b88..6a1ec04a 100644 --- a/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMParameterObjects.ps1 @@ -56,12 +56,12 @@ Function New-ARMParameterObjects [Parameter(Position=2, Mandatory=$true)] [string]$Name, [Parameter(Position=3, Mandatory=$true)] [string]$Region, [Parameter(Position=4, Mandatory=$true)] [string]$ImplementationPerformance, - [Parameter(Position=5, Mandatory=$false)] [string]$PublisherName = "", - [Parameter(Position=6, Mandatory=$false)] [string]$PublisherEmail = "", + [Parameter(Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("None", "MicrosoftEntraId")] - [Parameter(Position=7, Mandatory=$false)] [string]$RestSourceAuthentication = "None", - [Parameter(Position=8, Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", - [Parameter(Position=9, Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "" + [Parameter(Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", [Parameter()] [switch]$ForUpdate ) @@ -206,10 +206,10 @@ Function New-ARMParameterObjects isZoneRedundant = $false } ) - $ApiManagementSku = "BasicV2" + $ApiManagementSku = "Basicv2" } "Enhanced" { - $AppConfigSku = "Premium" + $AppConfigSku = "Standard" $KeyVaultSKU = "Standard" $StorageAccountPerformance = "Standard_GZRS" $AspSku = "P1V2" @@ -496,12 +496,13 @@ Function New-ARMParameterObjects foreach ($object in $ARMObjects) { ## Converts the structure of the variable to a JSON file. Write-Verbose -Message "Creating the Parameter file for $($Object.ObjectType) in the following location: $($Object.ParameterPath)" - $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 - if ($ForUpdate) { - $ParameterFile | Out-File -FilePath $Object.ParameterPath -NoClobber -ErrorAction SilentlyContinue + + if ($ForUpdate -and (Test-Path -Path $Object.ParameterPath)) { + $Object.Parameters = Get-Content -Path $Object.ParameterPath -Raw | ConvertFrom-Json } else { - $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force + $ParameterFileContent = $Object.Parameters | ConvertTo-Json -Depth 8 + $ParameterFileContent | Out-File -FilePath $Object.ParameterPath -Force } } diff --git a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 index 4aedb327..335afd84 100644 --- a/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/New-WinGetSource.ps1 @@ -26,7 +26,7 @@ Function New-WinGetSource [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplates) .PARAMETER ParameterOutputPath - [Optional] The directory where Parameter objects will be created in. (Default: Current Directory\Parameters) + [Optional] The directory where Parameter files will be created in. (Default: Current Directory\Parameters) .PARAMETER RestSourcePath [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip) @@ -78,21 +78,21 @@ Function New-WinGetSource #> PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$Name, - [Parameter(Position=1, Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", - [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "", - [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", - [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", - [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", - [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", - [Parameter(Position=7, Mandatory=$false)] [string]$PublisherName = "", - [Parameter(Position=8, Mandatory=$false)] [string]$PublisherEmail = "", + [Parameter(Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Mandatory=$false)] [string]$Region = "westus", + [Parameter(Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", + [Parameter(Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", + [Parameter(Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", + [Parameter(Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("Developer", "Basic", "Enhanced")] - [Parameter(Position=9, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [Parameter(Mandatory=$false)] [string]$ImplementationPerformance = "Basic", [ValidateSet("None", "MicrosoftEntraId")] - [Parameter(Position=10,Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter(Mandatory=$false)] [string]$RestSourceAuthentication = "None", [Parameter()] [switch]$CreateNewMicrosoftEntraIdAppRegistration, - [Parameter(Position=11,Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", - [Parameter(Position=12,Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", [Parameter()] [switch]$ShowConnectionInstructions ) @@ -100,6 +100,10 @@ Function New-WinGetSource Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" } + $TemplateFolderPath = [System.IO.Path]::GetFullPath($TemplateFolderPath, $pwd.Path) + $ParameterOutputPath = [System.IO.Path]::GetFullPath($ParameterOutputPath, $pwd.Path) + $RestSourcePath = [System.IO.Path]::GetFullPath($RestSourcePath, $pwd.Path) + ############################### ## Check input paths if(!$(Test-Path -Path $TemplateFolderPath)) { diff --git a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 index 085f6d7c..97df1f49 100644 --- a/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/Remove-WinGetManifest.ps1 @@ -33,8 +33,8 @@ Function Remove-WinGetManifest PARAM( [Parameter(Position=0, Mandatory=$true)] [string]$FunctionName, [Parameter(Position=1, Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [string]$PackageIdentifier, - [Parameter(Position=2, Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [string]$PackageVersion = "", - [Parameter(Position=3, Mandatory=$false)] [string]$SubscriptionName = "" + [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true)] [string]$PackageVersion = "", + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "" ) BEGIN { diff --git a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 index 739526ec..5eaa734e 100644 --- a/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 +++ b/Tools/PowershellModule/src/Library/Test-ConnectionToAzure.ps1 @@ -32,8 +32,8 @@ Function Test-ConnectionToAzure #> PARAM( - [Parameter(Position=0, Mandatory=$false)] [string] $SubscriptionName = "", - [Parameter(Position=1, Mandatory=$false)] [string] $SubscriptionId = "" + [Parameter(Mandatory=$false)] [string] $SubscriptionName = "", + [Parameter(Mandatory=$false)] [string] $SubscriptionId = "" ) $Result = $false diff --git a/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 b/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 index 4aedb327..97163218 100644 --- a/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 +++ b/Tools/PowershellModule/src/Library/Update-WinGetSource.ps1 @@ -1,32 +1,30 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Function New-WinGetSource +Function Update-WinGetSource { <# .SYNOPSIS - Creates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. + Updates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. .DESCRIPTION - Creates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. + Updates a Windows Package Manager REST source in Azure for the storage of Windows Package Manager package Manifests. .PARAMETER Name - The name of the objects that will be created + The name of the objects that will be updated .PARAMETER ResourceGroup [Optional] The name of the Resource Group that the Windows Package Manager REST source will reside. All Azure - resources will be created in in this Resource Group (Default: WinGetRestsource) + resources will be updated in in this Resource Group (Default: WinGetRestsource) .PARAMETER SubscriptionName [Optional] The name of the subscription that will be used to host the Windows Package Manager REST source. (Default: Current connected subscription) - .PARAMETER Region - [Optional] The Azure location where objects will be created in. (Default: westus) - .PARAMETER TemplateFolderPath [Optional] The directory containing required ARM templates. (Default: $PSScriptRoot\..\Data\ARMTemplates) .PARAMETER ParameterOutputPath - [Optional] The directory where Parameter objects will be created in. (Default: Current Directory\Parameters) + [Optional] The directory containing Parameter files. If existing Parameter files are found, they will be used for WinGetRestSource update without modification. + If Parameter files are not found, new Parameter files with default values will be created. (Default: Current Directory\Parameters) .PARAMETER RestSourcePath [Optional] Path to the compiled REST API Zip file. (Default: $PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip) @@ -45,62 +43,64 @@ Function New-WinGetSource | Basic | Specifies a basic functioning Windows Package Manager REST source. | | Enhanced | Specifies a higher tier functionality with data replication across multiple data centers. | + Note for updating WinGet source, performance downgrading is not allowed by Azure resources used in WinGet source. + (Default: Basic) .PARAMETER RestSourceAuthentication [Optional] ["None", "MicrosoftEntraId"] The WinGet rest source authentication type. [Default: None] - .PARAMETER CreateNewMicrosoftEntraIdAppRegistration - [Optional] If specified, a new Microsoft Entra Id app registration will be created. (Default: False) - .PARAMETER MicrosoftEntraIdResource [Optional] Microsoft Entra Id authentication resource .PARAMETER MicrosoftEntraIdResourceScope [Optional] Microsoft Entra Id authentication resource scope - .PARAMETER ShowConnectionInstructions - [Optional] If specified, the instructions for connecting to the Windows Package Manager REST source. (Default: False) + .PARAMETER FunctionName + [Optional] The Azure Function name. Required if PublishAzureFunctionOnly is specified. + + .PARAMETER PublishAzureFunctionOnly + [Optional] If specified, only does Azure Function publish. .EXAMPLE - New-WinGetSource -Name "contosorestsource" + Update-WinGetSource-WinGetSource -Name "contosorestsource" - Creates the Windows Package Manager REST source in Azure with resources named "contosorestsource" in the westus region of - Azure with the basic level performance. + Updates the Windows Package Manager REST source in Azure with resources named "contosorestsource" with the basic level performance. .EXAMPLE - New-WinGetSource -Name "contosorestsource" -ResourceGroup "WinGet" -SubscriptionName "Visual Studio Subscription" -Region "westus" -ParameterOutput "C:\WinGet" -ImplementationPerformance "Basic" -ShowConnectionInformation + Update-WinGetSource-WinGetSource -Name "contosorestsource" -ResourceGroup "WinGet" -SubscriptionName "Visual Studio Subscription" -ParameterOutput "C:\WinGet" -ImplementationPerformance "Basic" - Creates the Windows Package Manager REST source in Azure with resources named "contosorestsource" in the westus region of - Azure with the basic level performance in the "Visual Studio Subscription" Subscription. Displays the required command - to connect the WinGet client to the new REST source after the REST source has been created. + Updates the Windows Package Manager REST source in Azure with resources named "contosorestsource" with the basic level performance in the "Visual Studio Subscription" Subscription. + + .EXAMPLE + Update-WinGetSource-WinGetSource -Name "contosorestsource" -PublishAzureFunctionOnly + + Updates the Windows Package Manager REST source in Azure with resources named "contosorestsource" with publishing Azure Function only. #> PARAM( - [Parameter(Position=0, Mandatory=$true)] [string]$Name, - [Parameter(Position=1, Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", - [Parameter(Position=2, Mandatory=$false)] [string]$SubscriptionName = "", - [Parameter(Position=3, Mandatory=$false)] [string]$Region = "westus", - [Parameter(Position=4, Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", - [Parameter(Position=5, Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", - [Parameter(Position=6, Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", - [Parameter(Position=7, Mandatory=$false)] [string]$PublisherName = "", - [Parameter(Position=8, Mandatory=$false)] [string]$PublisherEmail = "", + [Parameter(Position=0, Mandatory=$true)] [string]$Name, + [Parameter(Mandatory=$false)] [string]$ResourceGroup = "WinGetRestSource", + [Parameter(Mandatory=$false)] [string]$SubscriptionName = "", + [Parameter(Mandatory=$false)] [string]$TemplateFolderPath = "$PSScriptRoot\..\Data\ARMTemplates", + [Parameter(Mandatory=$false)] [string]$ParameterOutputPath = "$($(Get-Location).Path)\Parameters", + [Parameter(Mandatory=$false)] [string]$RestSourcePath = "$PSScriptRoot\..\Data\WinGet.RestSource.Functions.zip", + [Parameter(Mandatory=$false)] [string]$PublisherName = "", + [Parameter(Mandatory=$false)] [string]$PublisherEmail = "", [ValidateSet("Developer", "Basic", "Enhanced")] - [Parameter(Position=9, Mandatory=$false)] [string]$ImplementationPerformance = "Basic", + [Parameter(Mandatory=$false)] [string]$ImplementationPerformance = "Basic", [ValidateSet("None", "MicrosoftEntraId")] - [Parameter(Position=10,Mandatory=$false)] [string]$RestSourceAuthentication = "None", - [Parameter()] [switch]$CreateNewMicrosoftEntraIdAppRegistration, - [Parameter(Position=11,Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", - [Parameter(Position=12,Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", - [Parameter()] [switch]$ShowConnectionInstructions + [Parameter(Mandatory=$false)] [string]$RestSourceAuthentication = "None", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResource = "", + [Parameter(Mandatory=$false)] [string]$MicrosoftEntraIdResourceScope = "", + [Parameter(Mandatory=$false)] [string]$FunctionName = "", + [Parameter()] [switch]$PublishAzureFunctionOnly ) - if($ImplementationPerformance -eq "Developer") { - Write-Warning "The ""Developer"" build creates the Azure Cosmos DB Account with the ""Free-tier"" option selected which offset the total cost. Only 1 Cosmos DB Account per tenant can make use of this tier.`n" - } + $TemplateFolderPath = [System.IO.Path]::GetFullPath($TemplateFolderPath, $pwd.Path) + $ParameterOutputPath = [System.IO.Path]::GetFullPath($ParameterOutputPath, $pwd.Path) + $RestSourcePath = [System.IO.Path]::GetFullPath($RestSourcePath, $pwd.Path) - ############################### ## Check input paths if(!$(Test-Path -Path $TemplateFolderPath)) { Write-Error "REST Source Function Code is missing in specified path ($TemplateFolderPath)" @@ -111,14 +111,6 @@ Function New-WinGetSource return $false } - ############################### - ## Check Microsoft Entra Id input - if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !$CreateNewMicrosoftEntraIdAppRegistration -and !$MicrosoftEntraIdResource) { - Write-Error "When Microsoft Entra Id authentication is requested, either CreateNewMicrosoftEntraIdAppRegistration should be requested or MicrosoftEntraIdResource should be provided." - return $false - } - - ############################### ## Create folder for the Parameter output path $Result = New-Item -ItemType Directory -Path $ParameterOutputPath -Force if($Result) { @@ -129,7 +121,6 @@ Function New-WinGetSource return $false } - ############################### ## Connects to Azure, if not already connected. Write-Information "Testing connection to Azure." $Result = Connect-ToAzure -SubscriptionName $SubscriptionName @@ -138,71 +129,71 @@ Function New-WinGetSource return $false } - ############################### - ## Create new Microsoft Entra Id app registration if requested - if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and $CreateNewMicrosoftEntraIdAppRegistration) { - $Result = New-MicrosoftEntraIdApp -Name $Name - if (!$Result.Result) { - Write-Error "Failed to create new Microsoft Entra Id app registration." + ## Check resource group exists + $GetResult = Get-AzResourceGroup -Name $ResourceGroup + if (!$GetResult) { + Write-Error "Failed to get Azure Resource Group. Name: $ResourceGroup" + return $false + } + $Region = $GetResult.Location + + if ($PublishAzureFunctionOnly) { + if ([string]::IsNullOrWhiteSpace($FunctionName)) { + Write-Error "FunctionName is null or empty" return $false } - else { - $MicrosoftEntraIdResource = $Result.Resource - $MicrosoftEntraIdResourceScope = $Result.ResourceScope - } - } - ############################### - ## Creates the ARM files - $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance -PublisherName $PublisherName -PublisherEmail $PublisherEmail -RestSourceAuthentication $RestSourceAuthentication -MicrosoftEntraIdResource $MicrosoftEntraIdResource -MicrosoftEntraIdResourceScope $MicrosoftEntraIdResourceScope - if (!$ARMObjects) { - Write-Error "Failed to create ARM parameter objects." - return $false - } + Write-Information -MessageData "Publishing function files to the Azure Function." + $DeployResult = Publish-AzWebApp -ArchivePath $RestSourcePath -ResourceGroupName $ResourceGroup -Name $FunctionName -Force -ErrorVariable DeployError - ############################### - ## Create Resource Group - Write-Information "Creating the Resource Group used to host the Windows Package Manager REST source. Name: $ResourceGroup, Region: $Region" - $Result = Add-AzureResourceGroup -Name $ResourceGroup -Region $Region - if (!$Result) { - Write-Error "Failed to create Azure resource group. Name: $ResourceGroup Region: $Region" - return $false - } + ## Verifies that no error occured when publishing the Function App + if ($DeployError -or !$DeployResult) { + $ErrReturnObject = @{ + DeployError = $DeployError + DeployResult = $DeployResult + } - #### Verifies ARM Parameters are correct - $Result = Test-ARMTemplates -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup - if($Result){ - $ErrReturnObject = @{ - ARMObjects = $ARMObjects - ResourceGroup = $ResourceGroup - Result = $Result + Write-Error "Failed to publishing the Function App. $DeployError" -TargetObject $ErrReturnObject + return $false } - Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject - return $false + ## Restart the Function App + Write-Verbose "Restarting Azure Function." + Restart-AzFunctionApp -Name $FunctionName -ResourceGroupName $ResourceGroup -Force } + else { + ## Check Microsoft Entra Id input + if ($RestSourceAuthentication -eq "MicrosoftEntraId" -and !$MicrosoftEntraIdResource) { + Write-Error "When Microsoft Entra Id authentication is requested, MicrosoftEntraIdResource should be provided." + return $false + } - ############################### - ## Creates Azure Objects with ARM Templates and Parameters - $Result = New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup - if (!$Result) { - Write-Error "Failed to create Azure resources for WinGet rest source" - return $false - } + ## Creates the ARM files + $ARMObjects = New-ARMParameterObjects -ParameterFolderPath $ParameterOutputPath -TemplateFolderPath $TemplateFolderPath -Name $Name -Region $Region -ImplementationPerformance $ImplementationPerformance -PublisherName $PublisherName -PublisherEmail $PublisherEmail -RestSourceAuthentication $RestSourceAuthentication -MicrosoftEntraIdResource $MicrosoftEntraIdResource -MicrosoftEntraIdResourceScope $MicrosoftEntraIdResourceScope -ForUpdate + if (!$ARMObjects) { + Write-Error "Failed to create ARM parameter objects." + return $false + } - ############################### - ## Shows how to connect local Windows Package Manager Client to newly created REST source - if($ShowConnectionInstructions) { - $ApiManagementName = $ARMObjects.Where({$_.ObjectType -eq "ApiManagement"}).Parameters.Parameters.serviceName.value - $ApiManagementURL = (Get-AzApiManagement -Name $ApiManagementName -ResourceGroupName $ResourceGroup).RuntimeUrl + #### Verifies ARM Parameters are correct + $Result = Test-ARMTemplates -ARMObjects $ARMObjects -ResourceGroup $ResourceGroup + if($Result){ + $ErrReturnObject = @{ + ARMObjects = $ARMObjects + ResourceGroup = $ResourceGroup + Result = $Result + } - ## Post script Run Informational: - #### Instructions on how to add the REST source to your Windows Package Manager Client - Write-Information -MessageData "Use the following command to register the new REST source with your Windows Package Manager Client:" -InformationAction Continue - Write-Information -MessageData " winget source add -n ""restsource"" -a ""$ApiManagementURL/winget/"" -t ""Microsoft.Rest""" -InformationAction Continue + Write-Error -Message "Testing found an error with the ARM template or parameter files. Error: $err" -TargetObject $ErrReturnObject + return $false + } - #### For more information about how to use the solution, visit the aka.ms link. - Write-Information -MessageData "`nFor more information on the Windows Package Manager Client, go to: https://aka.ms/winget-command-help`n" -InformationAction Continue + ## Creates Azure Objects with ARM Templates and Parameters + $Result = New-ARMObjects -ARMObjects $ARMObjects -RestSourcePath $RestSourcePath -ResourceGroup $ResourceGroup + if (!$Result) { + Write-Error "Failed to create Azure resources for WinGet rest source" + return $false + } } return $true diff --git a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 index 25530ea6..d59ff20c 100644 --- a/Tools/PowershellModule/src/Library/WinGetManifest.ps1 +++ b/Tools/PowershellModule/src/Library/WinGetManifest.ps1 @@ -176,22 +176,22 @@ class WinGetInstaller [string[]]$Capabilities [string[]]$RestricedCapabilities [string]$MSStoreProductIdentifier - [bool]$InstallerAbortsTerminal + [Nullable[bool]]$InstallerAbortsTerminal [string]$ReleaseDate - [bool]$InstallLocationRequired - [bool]$RequireExplicitUpgrade + [Nullable[bool]]$InstallLocationRequired + [Nullable[bool]]$RequireExplicitUpgrade [string]$ElevationRequirement [string[]]$UnsupportedOSArchitectures [WinGetAppsAndFeaturesEntry[]]$AppsAndFeaturesEntries [WinGetMarkets]$Markets [string]$NestedInstallerType [WinGetNestedInstallerFile[]]$NestedInstallerFiles - [bool]$DisplayInstallWarnings + [Nullable[bool]]$DisplayInstallWarnings [string[]]$UnsupportedArguments [WinGetInstallationMetadata]$InstallationMetadata - [bool]$DownloadCommandProhibited + [Nullable[bool]]$DownloadCommandProhibited [string]$RepairBehavior - [bool]$ArchiveBinariesDependOnPath + [Nullable[bool]]$ArchiveBinariesDependOnPath WinGetInstaller () {} } diff --git a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 index f271059c..fd73aa32 100644 --- a/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 +++ b/Tools/PowershellModule/src/Microsoft.WinGet.Source.psd1 @@ -34,7 +34,7 @@ # RequiredModules = @('Az') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Find-WinGetManifest") + FunctionsToExport = @("Add-WinGetManifest", "Get-WinGetManifest", "Find-WinGetManifest", "Remove-WinGetManifest", "New-WinGetSource", "Update-WinGetSource") # Cmdlets to export from this module, for best performance, do not use wild cards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() From 126ea10f9408556f1e62ac18e1e78d8be12b445d Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:04:45 -0800 Subject: [PATCH 16/16] ReturnResponseUrl --- .../PowershellModule/src/Library/New-ARMObjects.ps1 | 12 +++++++----- .../WinGet.RestSource.PowershellSupport.csproj | 2 +- .../Utils/PackageManifestUtils.cs | 4 +--- .../WinGet.RestSource.Utils.csproj | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 index 59d3eee8..f13361ff 100644 --- a/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 +++ b/Tools/PowershellModule/src/Library/New-ARMObjects.ps1 @@ -105,6 +105,12 @@ Function New-ARMObjects } } + ## Set secret get for Api management service + Write-Verbose "Set keyvault secret access for Api Management service" + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $ApiManagement.Identity.PrincipalId -PermissionsToSecrets Get -BypassObjectIdValidation + ## There's no good way to check if the permission has been propagated through the system. And this permission is immediately needed in the next ARM deployment. + Start-Sleep -Seconds 10 + ## Update backend urls and re-create parameters file if needed if (!$ApiManagementParameters.backendUrls.value) { $ApiManagementParameters.backendUrls.value = $FunctionAppUrls @@ -112,10 +118,6 @@ Function New-ARMObjects $ParameterFile = $Object.Parameters | ConvertTo-Json -Depth 8 $ParameterFile | Out-File -FilePath $Object.ParameterPath -Force } - - ## Set secret get for Api management service - Write-Verbose "Set keyvault secret access for Api Management service" - Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $ApiManagement.Identity.PrincipalId -PermissionsToSecrets Get } ## Creates the Azure Resource @@ -153,7 +155,7 @@ Function New-ARMObjects ## Set keyvault secrets get permission Write-Verbose "Set keyvault secret access for Azure Function" - Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $FunctionApp.IdentityPrincipalId -PermissionsToSecrets Get + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroup -ObjectId $FunctionApp.IdentityPrincipalId -PermissionsToSecrets Get -BypassObjectIdValidation ## Assign cosmos db roles $CosmosAccount = Get-AzCosmosDBAccount -ResourceGroupName $ResourceGroup -Name $CosmosAccountName diff --git a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj index 1048287d..485f55cb 100644 --- a/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj +++ b/src/WinGet.RestSource.PowershellSupport/WinGet.RestSource.PowershellSupport.csproj @@ -44,7 +44,7 @@ - + diff --git a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs index f9220f4c..b0965cee 100644 --- a/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs +++ b/src/WinGet.RestSource.Utils/Utils/PackageManifestUtils.cs @@ -274,9 +274,7 @@ private static Utils.Models.Arrays.ExpectedReturnCodes AddExpectedReturnCodes(Li { InstallerReturnCode = sourceExpectedReturnCode.InstallerReturnCode, ReturnResponse = sourceExpectedReturnCode.ReturnResponse, - - // Todo: Needs WinGetUtilInterop update - // ReturnResponseUrl = sourceExpectedReturnCode.ReturnResponseUrl, + ReturnResponseUrl = sourceExpectedReturnCode.ReturnResponseUrl, }; expectedReturnCodes.Add(expectedReturnCode); } diff --git a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj index 21c987fc..dadca9df 100644 --- a/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj +++ b/src/WinGet.RestSource.Utils/WinGet.RestSource.Utils.csproj @@ -42,7 +42,7 @@ - +