Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address offline installation with prerelease option #136

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ websites
wekyb
Hmmss
MMdd
MMdd
MMdd
dbaeumer
toolsai
vsextensions
vsixmanifest
vspackage
23 changes: 17 additions & 6 deletions resources/Help/Microsoft.VSCode.Dsc/VSCodeExtension.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: <https://code.visualstudio.com/docs/editor/extension-marketplace#_find-and-install-an-extension> |
| `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'
Expand All @@ -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'
Expand All @@ -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
Expand All @@ -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\[email protected]"
}
Invoke-DscResource -Name VSCodeExtension -Method Set -Property $params -ModuleName Microsoft.VSCode.Dsc
```
188 changes: 179 additions & 9 deletions resources/Microsoft.VSCode.Dsc/Microsoft.VSCode.Dsc.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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`.

Expand Down Expand Up @@ -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 {
Expand All @@ -208,6 +330,9 @@ class VSCodeExtension {
[DscProperty()]
[bool] $Exist = $true

[DscProperty()]
[bool] $PreRelease = $false

[DscProperty()]
[bool] $Insiders = $false

Expand All @@ -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
Expand All @@ -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
}
}

Expand All @@ -266,6 +432,10 @@ class VSCodeExtension {
return $false
}

if ($null -ne $this.PreRelease -and $this.PreRelease -ne $currentState.PreRelease) {
return $false
}

return $true
}

Expand Down Expand Up @@ -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)
}

Expand Down
Loading
Loading