├── global.json ├── .gitignore ├── .config └── tsaoptions.json ├── version.json ├── CODE_OF_CONDUCT.md ├── SUPPORT.md ├── LICENSE ├── SECURITY.md ├── README.md └── Microsoft.VisualStudio.DSC ├── Microsoft.VisualStudio.DSC.psd1 └── Microsoft.VisualStudio.DSC.psm1 /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Microsoft.Build.NoTargets" : "3.7.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /.vs 6 | -------------------------------------------------------------------------------- /.config/tsaoptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "codebaseName": "VisualStudioDSC", 3 | "instanceUrl": "https://devdiv.visualstudio.com/defaultcollection", 4 | "projectName": "DevDiv", 5 | "areaPath": "DevDiv\\VS Setup", 6 | "iterationPath": "DevDiv", 7 | "allTools": true 8 | } 9 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.0", 4 | "publicReleaseRefSpec": [ 5 | "^refs/heads/main$" 6 | ], 7 | "cloudBuild": { 8 | "buildNumber": { 9 | "enabled": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please [follow the instructions on Visual Studio Developer Commnunity](https://developercommunity.visualstudio.com/VisualStudio/report) to ask using the Visual Studio Installer. 10 | 11 | ## Microsoft Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual Studio Desired State Configuration (DSC) Resource Provider 2 | 3 | DSC resources to simplify state management of installed Visual Studio instances. 4 | 5 | ## VSComponents Resource 6 | 7 | The `VSComponents` resource is used to modify a pre-existing Visual Studio instance in order to add additional [components](https://learn.microsoft.com/visualstudio/install/workload-and-component-ids). It is meant to accompany the [`winget configure`](https://learn.microsoft.com/windows/package-manager/winget/configure) command. 8 | 9 | You currently need administrator permissions to use this resource to install or modify Visual Studio. Furthermore, Visual Studio must be closed in order to update or add components to it. 10 | 11 | Refer to [Use winget to install or modify Visual Studio](https://learn.microsoft.com/visualstudio/install/use-command-line-parameters-to-install-visual-studio?#use-winget-to-install-or-modify-visual-studio) for additional information. 12 | 13 | ### Parameters 14 | 15 | At least `VSConfigFile` or `Components` must be specified. You can also specify both simultaneously. 16 | 17 | **Parameter**|**Attribute**|**DataType**|**Description**|**Allowed Values** 18 | :-----|:-----|:-----|:-----|:----- 19 | `ProductId`|Key|String|The product identifier of the instance you are working with. EG: `Microsoft.VisualStudio.Product.Community`|See [workload and component ids](https://learn.microsoft.com/visualstudio/install/workload-and-component-ids) 20 | `ChannelId`|Key|String|The channel identifier of the instance you are working with. EG: `VisualStudio.17.Release`|See [channel identifiers](https://learn.microsoft.com/visualstudio/install/command-line-parameter-examples#using---channeluri) 21 | `Components`|Optional|StringArray[]|Collection of component identifiers you wish to update the provided instance with.|See [workload and component ids](https://learn.microsoft.com/visualstudio/install/workload-and-component-ids) 22 | `VSConfigFile`|Optional|String|Path to the [Installation Configuration (VSConfig) file](https://learn.microsoft.com/visualstudio/install/import-export-installation-configurations) you wish to update the provided instance with.|Valid file path to a .vsconfig file 23 | `IncludeRecommended`|Optional|Boolean|For the provided required components, also add recommended components into the specified instance|True/False 24 | `IncludeOptional`|Optional|Boolean|For the provided required components, also add optional components into the specified instance|True/False 25 | `AllowUnsignedExtensions`|Optional|Boolean|For the provided extensions, allow installing unsigned extensions into the specified instance|True/False 26 | `InstalledComponents`|NotConfigurable|StringArray[]|A collection of components installed in the Visual Studio instance identified by the provided Product ID and Channel ID.|N/A 27 | 28 | 29 | ## Contributing 30 | 31 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 32 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 33 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 34 | 35 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 36 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 37 | provided by the bot. You will only need to do this once across all repos using our CLA. 38 | 39 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 40 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 41 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 42 | 43 | ## Trademarks 44 | 45 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 46 | trademarks or logos is subject to and must follow 47 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 48 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 49 | Any use of third-party trademarks or logos are subject to those third-party's policies. 50 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.DSC/Microsoft.VisualStudio.DSC.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'Microsoft.VisualStudio.DSC' 3 | # 4 | # Generated by: Microsoft Corporation 5 | # 6 | # Generated on: 5/4/2023 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Microsoft.VisualStudio.DSC.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.0.0' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'e5906a64-2d3f-4721-913d-652f6adbc8cb' 22 | 23 | # Author of this module 24 | Author = 'Visual Studio Setup Team' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Microsoft Corporation' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) Microsoft Corporation. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'DSC resources to simplify state management of installed Visual Studio instances' 34 | 35 | # Minimum version of the PowerShell engine required by this module 36 | PowerShellVersion = '5.1' 37 | 38 | # Name of the PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # ClrVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # 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. 72 | FunctionsToExport = @() 73 | 74 | # Cmdlets 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 cmdlets to export. 75 | # Commented out due to https://github.com/PowerShell/PowerShell/issues/12054#issuecomment-1253925630 76 | #CmdletsToExport = @() 77 | 78 | # Variables to export from this module 79 | VariablesToExport = '*' 80 | 81 | # 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. 82 | # Commented out due to https://github.com/PowerShell/PowerShell/issues/12054#issuecomment-1253925630 83 | #AliasesToExport = @() 84 | 85 | # DSC resources to export from this module 86 | DscResourcesToExport = @( 87 | 'VSComponents' 88 | ) 89 | 90 | # List of all modules packaged with this module 91 | # ModuleList = @() 92 | 93 | # List of all files packaged with this module 94 | # FileList = @() 95 | 96 | # 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. 97 | PrivateData = @{ 98 | 99 | PSData = @{ 100 | 101 | # Tags applied to this module. These help with module discovery in online galleries. 102 | Tags = @('PSDscResource_VSComponents') 103 | 104 | # A URL to the license for this module. 105 | LicenseUri = 'https://github.com/microsoft/VisualStudioDSC/blob/main/LICENSE' 106 | 107 | # A URL to the main website for this project. 108 | ProjectUri = 'https://github.com/microsoft/VisualStudioDSC' 109 | 110 | # A URL to an icon representing this module. 111 | # IconUri = '' 112 | 113 | # ReleaseNotes of this module 114 | # ReleaseNotes = '' 115 | 116 | # Prerelease string of this module 117 | # Prerelease = '' 118 | 119 | # Flag to indicate whether the module requires explicit user acceptance for install/update/save 120 | # RequireLicenseAcceptance = $false 121 | 122 | # External dependent modules of this module 123 | # ExternalModuleDependencies = @() 124 | 125 | } # End of PSData hashtable 126 | 127 | } # End of PrivateData hashtable 128 | 129 | # HelpInfo URI of this module 130 | # HelpInfoURI = '' 131 | 132 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 133 | # DefaultCommandPrefix = '' 134 | 135 | } 136 | 137 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.DSC/Microsoft.VisualStudio.DSC.psm1: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. All rights reserved. 2 | # Licensed under the MIT License. 3 | 4 | #Requires -RunAsAdministrator 5 | 6 | using namespace System.Collections.Generic 7 | using namespace System.Diagnostics 8 | 9 | [DSCResource()] 10 | class VSComponents 11 | { 12 | [DscProperty(Key)] 13 | [string]$productId 14 | 15 | [DscProperty(Key)] 16 | [string]$channelId 17 | 18 | [DscProperty()] 19 | [string[]]$components 20 | 21 | [DscProperty()] 22 | [string]$vsConfigFile 23 | 24 | [DscProperty()] 25 | [bool]$includeRecommended = $false 26 | 27 | [DscProperty()] 28 | [bool]$includeOptional = $false 29 | 30 | [DscProperty()] 31 | [bool]$allowUnsignedExtensions = $false 32 | 33 | [DscProperty(NotConfigurable)] 34 | [string[]]$installedComponents 35 | 36 | [VSComponents] Get() 37 | { 38 | $this.installedComponents = Get-VsComponents -ProductId $this.productId -ChannelId $this.channelId 39 | 40 | return @{ 41 | productId = $this.productId 42 | channelId = $this.channelId 43 | components = $this.components 44 | vsConfigFile = $this.vsConfigFile 45 | includeRecommended = $this.includeRecommended 46 | includeOptional = $this.includeOptional 47 | allowUnsignedExtensions = $this.allowUnsignedExtensions 48 | installedComponents = $this.installedComponents 49 | } 50 | } 51 | 52 | [bool] Test() 53 | { 54 | if(-not $this.components -and -not $this.vsConfigFile) 55 | { 56 | throw "No components specified to be added. Specify either an Installation Configuration (VSConfig) file, individual required components, or both." 57 | } 58 | 59 | $this.Get() 60 | $requestedComponents = $this.components 61 | 62 | if($this.vsConfigFile) 63 | { 64 | if(-not (Test-Path $this.vsConfigFile)) 65 | { 66 | throw "Provided Installation Configuration file does not exist at $($this.vsConfigFile)" 67 | } 68 | 69 | $vsConfigFileObj = Get-Content $this.vsConfigFile | Out-String | ConvertFrom-Json 70 | 71 | # If the provided VS Config file has extensions, automatically fail the test 72 | if($vsConfigFileObj.extensions.count -gt 0) 73 | { 74 | return $false 75 | } 76 | 77 | $requestedComponents += $vsConfigFileObj | Select-Object -ExpandProperty components 78 | } 79 | 80 | foreach ($component in $requestedComponents) 81 | { 82 | if($this.installedComponents -notcontains $component) 83 | { 84 | return $false 85 | } 86 | } 87 | 88 | return $true 89 | } 90 | 91 | [void] Set() 92 | { 93 | if ($this.Test()) 94 | { 95 | return 96 | } 97 | 98 | Add-VsComponents -ProductId $this.productId -ChannelId $this.channelId -VsConfigPath $this.vsConfigFile -Components $this.components -IncludeRecommended $this.includeRecommended -IncludeOptional $this.includeOptional -AllowUnsignedExtensions $this.allowUnsignedExtensions 99 | } 100 | } 101 | 102 | <# 103 | .SYNOPSIS 104 | Returns a collection of components identifiers installed in the Visual Studio instance identified by the provided Product ID and Channel ID. 105 | 106 | .PARAMETER ProductId 107 | The product identifier of the instance you are working with. EG: 'Microsoft.VisualStudio.Product.Community' 108 | 109 | .PARAMETER ChannelId 110 | The channel identifier of the instance you are working with. EG: 'VisualStudio.17.Release' 111 | 112 | .LINK 113 | https://learn.microsoft.com/en-us/visualstudio/install/workload-and-component-ids 114 | 115 | .LINK 116 | https://learn.microsoft.com/en-us/visualstudio/install/command-line-parameter-examples#using---channelId 117 | #> 118 | function Get-VsComponents 119 | { 120 | param 121 | ( 122 | [Parameter(Mandatory)] 123 | [string]$ProductId, 124 | 125 | [Parameter(Mandatory)] 126 | [string]$ChannelId 127 | ) 128 | 129 | $result = Invoke-VsWhere -Arguments "-products $ProductId -include packages -format json -all -prerelease" | ConvertFrom-Json | Where-Object { $_.channelId -eq $ChannelId } 130 | return $result.packages | Where-Object { $_.type -eq "Component" -or $_.type -eq "Workload" } | Select-Object -ExpandProperty id 131 | } 132 | 133 | <# 134 | .SYNOPSIS 135 | Adds components and workloads identified by the provided component list & Installation Configuration (VSConfig) file into the specified instance 136 | 137 | .PARAMETER ProductId 138 | The product identifier of the instance you are working with. EG: 'Microsoft.VisualStudio.Product.Community' 139 | 140 | .PARAMETER ChannelId 141 | The channel identifier of the instance you are working with. EG: 'VisualStudio.17.Release' 142 | 143 | .PARAMETER Components 144 | Collection of component identifiers you wish to update the provided instance with. 145 | 146 | .PARAMETER VsConfigPath 147 | Path to the Installation Configuration (VSConfig) file you wish to update the provided instance with. 148 | 149 | .PARAMETER IncludeRecommended 150 | For the provided required components, also add recommended components into the specified instance 151 | 152 | .PARAMETER IncludeOptional 153 | For the provided required components, also add optional components into the specified instance 154 | 155 | .PARAMETER AllowUnsignedExtensions 156 | For the provided extensions, allow unsigned extensions to be installed into the specified instance 157 | 158 | .LINK 159 | https://learn.microsoft.com/en-us/visualstudio/install/workload-and-component-ids 160 | 161 | .LINK 162 | https://learn.microsoft.com/en-us/visualstudio/install/command-line-parameter-examples#using---channelId 163 | 164 | .LINK 165 | https://learn.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio#install-update-modify-repair-uninstall-and-export-commands-and-command-line-parameters 166 | 167 | .LINK 168 | https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/ 169 | #> 170 | function Add-VsComponents 171 | { 172 | param 173 | ( 174 | [Parameter(Mandatory)] 175 | [string]$ProductId, 176 | 177 | [Parameter(Mandatory)] 178 | [string]$ChannelId, 179 | 180 | [Parameter()] 181 | [string[]]$Components, 182 | 183 | [Parameter()] 184 | [string]$VsConfigPath, 185 | 186 | [Parameter()] 187 | [bool]$IncludeRecommended, 188 | 189 | [Parameter()] 190 | [bool]$IncludeOptional, 191 | 192 | [Parameter()] 193 | [bool]$AllowUnsignedExtensions 194 | ) 195 | 196 | $installerArgs = "modify --productId $ProductId --channelId $ChannelId --quiet --norestart --activityId VisualStudioDSC-$((New-Guid).Guid)" 197 | 198 | if(-not $Components -and -not $VsConfigPath) 199 | { 200 | throw "No components specified to be added. Specify either an Installation Configuration (VSConfig) file, individual required components, or both." 201 | } 202 | 203 | if($VsConfigPath) 204 | { 205 | if(-not (Test-Path $VsConfigPath)) 206 | { 207 | throw "Provided Installation Configuration file does not exist at $VsConfigPath" 208 | } 209 | 210 | $installerArgs += " --config `"$VsConfigPath`"" 211 | } 212 | 213 | if($Components) 214 | { 215 | $installerArgs += " --add " + ($Components -join ' --add '); 216 | } 217 | 218 | if($IncludeRecommended) 219 | { 220 | $installerArgs += " --includeRecommended" 221 | } 222 | 223 | 224 | if($IncludeOptional) 225 | { 226 | $installerArgs += " --includeOptional" 227 | } 228 | 229 | if($AllowUnsignedExtensions) 230 | { 231 | $installerArgs += " --allowUnsignedExtensions" 232 | } 233 | 234 | Invoke-VsInstaller -Arguments $installerArgs 235 | } 236 | 237 | <# 238 | .SYNOPSIS 239 | Builds a base path, if it exists, with the provided argument. 240 | 241 | .DESCRIPTION 242 | Builds a base path with the provided argument. 243 | This is used to build a base path for process ids of setup.exe or vswhere.exe. 244 | 245 | .PARAMETER Arguments 246 | Arguments to build a base path with 247 | #> 248 | function Build-BasePath 249 | { 250 | param 251 | ( 252 | [Parameter()] 253 | [string]$ExePath 254 | ) 255 | 256 | $basePath = Join-Path -Path "${env:ProgramFiles(x86)}" -ChildPath "Microsoft Visual Studio" 257 | 258 | if($ExePath) 259 | { 260 | return Join-Path -Path $basePath -ChildPath $ExePath 261 | } 262 | 263 | return $basePath 264 | } 265 | 266 | <# 267 | .SYNOPSIS 268 | Invokes Visual Studio Installer, if it exists, with the provided arguments. 269 | 270 | .DESCRIPTION 271 | Invokes Visual Studio Installer with the provided arguments. 272 | If this script is not run as an administrator, without the installer present, or with the installer process running, this script will fail. 273 | The invocation is considered successful if return codes of 0 (success), 3010 (reboot required) or 862968 (reboot recommended) are returned. 274 | 275 | .PARAMETER Arguments 276 | Arguments to pass onwards to Visual Studio Installer. 277 | 278 | .LINK 279 | https://learn.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio 280 | #> 281 | function Invoke-VsInstaller 282 | { 283 | param 284 | ( 285 | [Parameter(Mandatory)] 286 | [string]$Arguments 287 | ) 288 | 289 | Assert-IsAdministrator 290 | Assert-VsInstallerPresent 291 | Assert-VsInstallerProcessNotRunning 292 | 293 | $installer = Start-Process -FilePath (Get-VsInstallerPath) -ArgumentList $Arguments -PassThru 294 | $installer.WaitForExit(); 295 | $basePath = Build-BasePath 296 | # Set EnableRaisingEvents to true to access the Exit Code later 297 | $activeInstallerProcess = Get-Process Setup | Where-Object { $_.Path -like "$basePath*" } | ForEach-Object { $_.EnableRaisingEvents = $true } 298 | # See script block description for error code explanation 299 | $validErrorCodes = 0,3010,862968; 300 | 301 | if($activeInstallerProcess) 302 | { 303 | $processIds = $activeInstallerProcess | Select-Object -ExpandProperty Id 304 | Wait-Process -Id $processIds -Timeout 3600 305 | 306 | foreach($process in $activeInstallerProcess) 307 | { 308 | if($process.ExitCode -NotIn $validErrorCodes) 309 | { 310 | throw "Visual Studio Installer failed after installer update with error code $($process.ExitCode) using arguments: $Arguments" 311 | } 312 | } 313 | } 314 | else 315 | { 316 | if($installer.ExitCode -NotIn $validErrorCodes) 317 | { 318 | throw "Visual Studio Installer failed with error code $($installer.ExitCode) using arguments: $Arguments" 319 | } 320 | } 321 | } 322 | 323 | <# 324 | .SYNOPSIS 325 | Invokes Visual Studio Locator, if it exists, with the provided arguments. 326 | 327 | .DESCRIPTION 328 | Invokes Visual Studio Locator (vswhere.exe) with the provided arguments. 329 | If this script is run without the locator present, it will fail. 330 | 331 | .PARAMETER Arguments 332 | Arguments to pass onwards to Visual Studio Locator. 333 | 334 | .LINK 335 | https://learn.microsoft.com/en-us/visualstudio/install/tools-for-managing-visual-studio-instances#using-vswhereexe 336 | #> 337 | function Invoke-VsWhere 338 | { 339 | param 340 | ( 341 | [Parameter(Mandatory)] 342 | [string]$Arguments 343 | ) 344 | 345 | Assert-VsWherePresent 346 | 347 | return Invoke-Expression -Command "&'$(Get-VsWherePath)' $Arguments" 348 | } 349 | 350 | <# 351 | .SYNOPSIS 352 | Throws an exception if not running elevated. 353 | #> 354 | function Assert-IsAdministrator 355 | { 356 | if(-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) 357 | { 358 | throw "This resource must be run as an Administrator." 359 | } 360 | } 361 | 362 | <# 363 | .SYNOPSIS 364 | Returns the default path of Visual Studio Installer. 365 | #> 366 | function Get-VsInstallerPath 367 | { 368 | return Build-BasePath -ExePath "Installer\setup.exe" 369 | } 370 | 371 | <# 372 | .SYNOPSIS 373 | Returns the default path of Visual Studio Locator (vswhere.exe). 374 | #> 375 | function Get-VsWherePath 376 | { 377 | return Build-BasePath -ExePath "Installer\vswhere.exe" 378 | } 379 | 380 | <# 381 | .SYNOPSIS 382 | Throws an exception if Visual Studio Installer is not present in the default location. 383 | #> 384 | function Assert-VsInstallerPresent 385 | { 386 | if(-not (Test-Path (Get-VsInstallerPath))) 387 | { 388 | throw "Visual Studio Installer not found." 389 | } 390 | } 391 | 392 | <# 393 | .SYNOPSIS 394 | Throws an exception if Visual Studio Locator (vswhere.exe) is not present in the default location. 395 | #> 396 | function Assert-VsWherePresent 397 | { 398 | if(-not (Test-Path (Get-VsWherePath))) 399 | { 400 | throw "Visual Studio Locator not found." 401 | } 402 | } 403 | 404 | <# 405 | .SYNOPSIS 406 | Throws an exception if Visual Studio Installer is currently running. 407 | #> 408 | function Assert-VsInstallerProcessNotRunning 409 | { 410 | if(Get-Process | Where-Object { $_.Path -eq (Get-VsInstallerPath) }) 411 | { 412 | throw "Visual Studio Installer is running. Close the installer and try again." 413 | } 414 | } 415 | --------------------------------------------------------------------------------