From 45679ecbf14a6cf6a8dda596203656217f041881 Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 6 Dec 2024 15:06:00 +0100 Subject: [PATCH 1/2] Address offline installation with prerelease option --- .github/actions/spelling/allow.txt | 3 +- .../Microsoft.VSCode.Dsc/VSCodeExtension.md | 23 ++- .../Microsoft.VSCode.Dsc.psm1 | 188 +++++++++++++++++- .../Microsoft.VSCode.Dsc.Tests.ps1 | 50 ++++- 4 files changed, 243 insertions(+), 21 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 1c720079..9cfe8327 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -67,4 +67,5 @@ websites wekyb Hmmss MMdd -MMdd \ No newline at end of file +MMdd +dbaeumer diff --git a/resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md b/resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md index 4f5e62b1..5c7a4f5c 100644 --- a/resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md +++ b/resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md @@ -1,7 +1,7 @@ --- external help file: Microsoft.VSCode.Dsc.psm1-Help.xml Module Name: Microsoft.VSCode.Dsc -ms.date: 10/22/2024 +ms.date: 12/06/2024 online version: schema: 2.0.0 title: VSCodeExtension @@ -24,11 +24,12 @@ The `VSCodeExtension` DSC Resource allows you to install, update, and remove Vis | `Name` | Key | String | The name of the Visual Studio Code extension to manage. | To find extensions in VSCode, check out: | | `Version` | Optional | String | The version of the Visual Studio Code extension to install. If not specified, the latest version will be installed. | For example: `1.0.0` | | `Exist` | Optional | Boolean | Indicates whether the extension should exist. The default value is `$true`. | `$true`, `$false` | +| `PreRelease` | Optional | Boolean | Indicates whether to install the pre-release version of the extension. The default value is `$false`. | `$true`, `$false` | | `Insiders` | Optional | Boolean | Indicates whether to manage the extension for the Insiders version of Visual Studio Code. The default value is `$false`. | `$true`, `$false` | ## EXAMPLES -### Example 1 +### EXAMPLE 1 - Install Python extension ```powershell # Install the latest version of the Visual Studio Code extension 'ms-python.python' @@ -38,7 +39,7 @@ $params = @{ Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc ``` -### EXAMPLE 2 +### EXAMPLE 2 - Install a particular version of the Python extension ```powershell # Install a specific version of the Visual Studio Code extension 'ms-python.python' @@ -49,7 +50,7 @@ $params = @{ Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc ``` -### EXAMPLE 3 +### EXAMPLE 3 - Uninstall Python extension ```powershell # Ensure the Visual Studio Code extension 'ms-python.python' is removed @@ -60,13 +61,23 @@ $params = @{ Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc ``` -### EXAMPLE 4 +### EXAMPLE 4 - Install Python extension in Visual Studio Code Insiders ```powershell -# Ensure the Visual Studio Code extension 'ms-python.python' is removed +# Ensure the Visual Studio Code extension 'ms-python.python' is installed in Insiders $params = @{ Name = 'ms-python.python' Insiders = $true } Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc ``` + +### EXAMPLE 5 - Install extension from file path + +```powershell +# Ensure the Visual Studio Code extension 'ms-python.python' is installed in Insiders +$params = @{ + Name = "C:\ShardExtensions\ms-toolsai.jupyter-latest@alpine-arm64.vsix" +} +Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc +``` diff --git a/resources/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.psm1 b/resources/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.psm1 index c05fe6da..d13bb20e 100644 --- a/resources/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.psm1 +++ b/resources/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.psm1 @@ -49,13 +49,105 @@ function Get-VSCodeCLIPath { throw 'VSCode is not installed.' } +function Get-PreReleaseFlag { + [CmdletBinding()] + param ( + [Parameter()] + [AllowNull()] + [string] $Name, + + [Parameter()] + [AllowNull()] + [string] $Version, + + [Parameter()] + [switch] $Insiders + ) + + if ($IsWindows) { + $packageName = [System.String]::Concat($Name, '-', $Version) + if ($Insiders) { + $extensionPath = Join-Path $env:USERPROFILE '.vscode-insiders' 'extensions' $packageName '.vsixmanifest' + } else { + $extensionPath = Join-Path $env:USERPROFILE '.vscode' 'extensions' $packageName '.vsixmanifest' + } + + if (Test-Path $extensionPath -ErrorAction SilentlyContinue) { + [xml]$manifest = Get-Content $extensionPath -ErrorAction SilentlyContinue + # If it does not contain the property, it is not a pre-release extension + if ($manifest.PackageManifest.Metadata.Properties.Property.Id -contains 'Microsoft.VisualStudio.Code.PreRelease') { + return $true + } else { + return $false + } + } + } +} + +function Get-VsixManifestInfo { + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param ( + [Parameter(Mandatory = $true)] + [string] $Path + ) + + [System.IO.Compression.ZipFile]::OpenRead($Path) | ForEach-Object { + $zipArchive = $_ + + $zipEntry = $zipArchive.Entries | Where-Object { $_.FullName -eq 'extension.vsixmanifest' } + + if ($zipEntry) { + $reader = [System.IO.StreamReader]::new($zipEntry.Open()) + [xml]$fileContent = $reader.ReadToEnd() + $reader.Close() + + $packageId = [System.String]::Concat($fileContent.PackageManifest.Metadata.Identity.Publisher, '.', $fileContent.PackageManifest.Metadata.Identity.Id) + return @{ + Name = $packageId + Version = $fileContent.PackageManifest.Metadata.Identity.Version + PreRelease = ($fileContent.PackageManifest.Metadata.Properties.Property.Id -contains 'Microsoft.VisualStudio.Code.PreRelease') + } + } else { + Throw "VSIX manifest not found. Ensure the VSIX file contains a 'extension.vsixmanifest' file." + } + + # Close the zip archive + $zipArchive.Dispose() + } +} + +function Test-VsixFilePath { + param ( + [Parameter(Mandatory = $false)] + [AllowNull()] + [string] $InputString + ) + + $extension = [System.IO.Path]::GetExtension($InputString) + + if ($extension -eq '.vsix') { + if (Test-Path -Path $InputString -ErrorAction SilentlyContinue) { + return $true + } else { + return $false + } + } else { + return $false + } +} + function Install-VSCodeExtension { [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string]$Name, + [Parameter(ValueFromPipelineByPropertyName)] - [string]$Version + [string]$Version, + + [Parameter()] + [bool]$PreRelease ) begin { @@ -75,7 +167,14 @@ function Install-VSCodeExtension { process { $installArgument = Get-VSCodeExtensionInstallArgument @PSBoundParameters - Invoke-VSCode -Command "--install-extension $installArgument" + # Always add the --force parameter to support switching between release and prerelease version + $command = "--install-extension $installArgument --force" + + if ($PreRelease) { + $command += ' --pre-release' + } + + Invoke-VSCode -Command $command } } @@ -158,6 +257,12 @@ function TryGetRegistryValue { .PARAMETER Exist Indicates whether the extension should exist. The default value is `$true`. +.PARAMETER PreRelease + Indicates whether to install the pre-release version of the extension. The default value is `$false`. + + When PreRelease is set to `$true`, the extension will be installed from the Visual Studio Code marketplace. If the extension is already installed, it will be updated to the pre-release version. + If there is no prerelease version available, the extension will be installed. + .PARAMETER Insiders Indicates whether to manage the extension for the Insiders version of Visual Studio Code. The default value is `$false`. @@ -196,6 +301,23 @@ function TryGetRegistryValue { PS C:\> Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc This installs the latest version of the Visual Studio Code extension 'ms-python.python' for the Insiders version of Visual Studio Code + +.EXAMPLE + PS C:\> $params = @{ + Name = 'dbaeumer.vscode-eslint' + PreRelease = $true + } + PS C:\> Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc + + This installs the latest pre-release version of the Visual Studio Code extension 'dbaeumer.vscode-eslint' + +.EXAMPLE + PS C:\> $params = @{ + Name = 'C:\SharedExtensions\ms-python.python-2021.5.842923320.vsix' + } + PS C:\> Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc + + This installs the Visual Studio Code extension 'ms-python.python' from the specified VSIX file path. #> [DSCResource()] class VSCodeExtension { @@ -208,6 +330,9 @@ class VSCodeExtension { [DscProperty()] [bool] $Exist = $true + [DscProperty()] + [bool] $PreRelease = $false + [DscProperty()] [bool] $Insiders = $false @@ -221,6 +346,12 @@ class VSCodeExtension { $this.Version = $Version } + VSCodeExtension([string]$Name, [string]$Version, [bool]$Insiders) { + $this.Name = $Name + $this.Version = $Version + $this.Insiders = $Insiders + } + [VSCodeExtension[]] Export([bool]$Insiders) { if ($Insiders) { $script:VSCodeCLIPath = Get-VSCodeCLIPath -Insiders @@ -234,25 +365,60 @@ class VSCodeExtension { for ($i = 0; $i -lt $extensionList.length; $i++) { $extensionName, $extensionVersion = $extensionList[$i] -Split '@' - $results[$i] = [VSCodeExtension]::new($extensionName, $extensionVersion) + $initialize = @{ + Name = $extensionName + Version = $extensionVersion + PreRelease = (Get-PreReleaseFlag -Name $extensionName -Version $extensionVersion -Insiders:$Insiders) + } + + if ($Insiders) { + $initialize.Insiders = $true + } + + $results[$i] = [VSCodeExtension]$initialize } return $results } [VSCodeExtension] Get() { + if (Test-VsixFilePath -InputString $this.Name) { + $manifestInfo = Get-VsixManifestInfo -Path $this.Name + $this.Name = $manifestInfo.Name + $this.Version = $manifestInfo.Version + $this.PreRelease = $manifestInfo.PreRelease + } + [VSCodeExtension]::GetInstalledExtensions($this.Insiders) $currentState = [VSCodeExtension]::InstalledExtensions[$this.Name] if ($null -ne $currentState) { - return [VSCodeExtension]::InstalledExtensions[$this.Name] + if ($null -ne $this.Version) { + $versionState = $currentState | Where-Object { $_.Version -eq $this.Version } + if ($versionState) { + $finalState = [VSCodeExtension]::InstalledExtensions[$this.Name] + } else { + $currentState.Exist = $false + $finalState = $currentState + } + } else { + $finalState = [VSCodeExtension]::InstalledExtensions[$this.Name] + } + + if ($currentState.PreRelease -ne $this.PreRelease) { + $currentState.Exist = $false + $finalState = $currentState + } + + return $finalState } return [VSCodeExtension]@{ - Name = $this.Name - Version = $this.Version - Exist = $false - Insiders = $this.Insiders + Name = $this.Name + Version = $this.Version + Exist = $false + PreRelease = $this.PreRelease + Insiders = $this.Insiders } } @@ -266,6 +432,10 @@ class VSCodeExtension { return $false } + if ($null -ne $this.PreRelease -and $this.PreRelease -ne $currentState.PreRelease) { + return $false + } + return $true } @@ -297,7 +467,7 @@ class VSCodeExtension { return } - Install-VSCodeExtension -Name $this.Name -Version $this.Version + Install-VSCodeExtension -Name $this.Name -Version $this.Version -PreRelease $this.PreRelease [VSCodeExtension]::GetInstalledExtensions($this.Insiders) } diff --git a/tests/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.Tests.ps1 b/tests/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.Tests.ps1 index 118cbe5a..f9c7fe9e 100644 --- a/tests/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.Tests.ps1 +++ b/tests/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.Tests.ps1 @@ -11,15 +11,21 @@ Set-StrictMode -Version Latest #> BeforeAll { - Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck + if ((Get-Module -Name PSDesiredStateConfiguration -ListAvailable).Version -ne '2.0.7') { + Write-Verbose -Message 'Installing PSDesiredStateConfiguration module.' -Verbose + Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck -RequiredVersion '2.0.7' + } + Import-Module Microsoft.VSCode.Dsc # Install VSCode - Invoke-WebRequest https://raw.githubusercontent.com/PowerShell/vscode-powershell/main/scripts/Install-VSCode.ps1 -UseBasicParsing -OutFile Install-VSCode.ps1 - .\Install-VSCode.ps1 -BuildEdition Stable-User -Confirm:$false + if ($env:TF_BUILD) { + Invoke-WebRequest https://raw.githubusercontent.com/PowerShell/vscode-powershell/main/scripts/Install-VSCode.ps1 -UseBasicParsing -OutFile Install-VSCode.ps1 + .\Install-VSCode.ps1 -BuildEdition Stable-User -Confirm:$false - # Install VSCode Insiders - .\Install-VSCode.ps1 -BuildEdition Insider-User -Confirm:$false + # Install VSCode Insiders + .\Install-VSCode.ps1 -BuildEdition Insider-User -Confirm:$false + } } Describe 'List available DSC resources' { @@ -60,6 +66,40 @@ Describe 'VSCodeExtension' { $finalState.Name | Should -Be $desiredState.Name $finalState.Exist | Should -BeTrue } + + It 'Sets desired extension from path' { + BeforeDiscovery { + $script:out = Join-Path ([System.IO.Path]::GetTempPath()) 'ms-toolsai.jupyter-latest.vsix' + $restParams = @{ + Uri = 'https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-toolsai/vsextensions/jupyter/latest/vspackage' + UseBasicParsing = $true + OutFile = $out + } + Invoke-RestMethod @restParams + } + + $desiredState = @{ + Name = 'ms-toolsai.jupyter' + } + + Invoke-DscResource -Name VSCodeExtension -ModuleName Microsoft.VSCode.Dsc -Method Set -Property $desiredState + $finalState = Invoke-DscResource -Name VSCodeExtension -ModuleName Microsoft.VSCode.Dsc -Method Get -Property $desiredState + $finalState.Name | Should -Be $desiredState.Name + $finalState.Exist | Should -BeTrue + } + + It 'Sets prerelease extension' { + $desiredState = @{ + Name = 'dbaeumer.vscode-eslint' + PreRelease = $true + } + + Invoke-DscResource -Name VSCodeExtension -ModuleName Microsoft.VSCode.Dsc -Method Set -Property $desiredState + $finalState = Invoke-DscResource -Name VSCodeExtension -ModuleName Microsoft.VSCode.Dsc -Method Get -Property $desiredState + $finalState.Name | Should -Be $desiredState.Name + $finalState.Exist | Should -BeTrue + $finalState.PreRelease | Should -BeTrue + } } Describe 'VSCodeExtension-Insiders' { From 2c66a38957b73fa79a89fe23f82089e31976abdc Mon Sep 17 00:00:00 2001 From: Gijs Reijn Date: Fri, 6 Dec 2024 15:07:59 +0100 Subject: [PATCH 2/2] Fix spelling --- .github/actions/spelling/allow.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 9cfe8327..d1800738 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -69,3 +69,7 @@ Hmmss MMdd MMdd dbaeumer +toolsai +vsextensions +vsixmanifest +vspackage