Question:
I’m trying to write a PowerShell script to write a Visual Studio extension which will simply add a project template. Here is a trimmed down version of the script to demonstrate the problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# Add the assemblies Add-Type -Assembly System.IO.Compression.FileSystem # Create temporary directories for the zip archives [System.IO.Directory]::CreateDirectory("Extension") [System.IO.Directory]::CreateDirectory("Template") # Build up the contents of the template file $templateContent = "`r`n" $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " $templateContent += " `r`n" $templateContent += " $templateContent += " $templateContent += " `r`n" $templateContent += "" # Save the template file $templateContent | Out-File ([System.IO.Path]::Combine("Template", "MyExtension.vstemplate")) -Encoding "UTF8" -NoNewline # Build up the contents of the proj file $projContent = "`r`n" $projContent += " $projContent += " $projContent += " $projContent += " $projContent += " $projContent += " $projContent += " `r`n" $projContent += " $projContent += " $projContent += " $projContent += " $projContent += " $projContent += " |
lt;/RootNamespace>r
n”
$projContent += ” <AssemblyName>$safeprojectname
1 |
lt;/AssemblyName>r
n”
$projContent += ” <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>r
n”
$projContent += ” <UseIISExpress>false</UseIISExpress>r
n”
$projContent += ” <IISExpressSSLPort />r
n”
$projContent += ” <IISExpressAnonymousAuthentication />r
n”
$projContent += ” <IISExpressWindowsAuthentication />r
n”
$projContent += ” <IISExpressUseClassicPipelineMode />r
n”
$projContent += ” </PropertyGroup>r
n”
$projContent += ” <PropertyGroup Condition=" '
$(Configuration)|$(Platform)' == 'Debug|AnyCPU'
“>r
n”
$projContent += ” <DebugSymbols>true</DebugSymbols>r
n”
$projContent += ” <DebugType>full</DebugType>r
n”
$projContent += ” <Optimize>false</Optimize>r
n”
$projContent += ” <OutputPath>bin\</OutputPath>r
n”
$projContent += ” <DefineConstants>DEBUG;TRACE</DefineConstants>r
n”
$projContent += ” <ErrorReport>prompt</ErrorReport>r
n”
$projContent += ” <WarningLevel>4</WarningLevel>r
n”
$projContent += ” </PropertyGroup>r
n”
$projContent += ” <PropertyGroup Condition=" '
$(Configuration)|$(Platform)' == 'Release|AnyCPU'
“>r
n”
$projContent += ” <DebugType>pdbonly</DebugType>r
n”
$projContent += ” <Optimize>true</Optimize>r
n”
$projContent += ” <OutputPath>bin\</OutputPath>r
n”
$projContent += ” <DefineConstants>TRACE</DefineConstants>r
n”
$projContent += ” <ErrorReport>prompt</ErrorReport>r
n”
$projContent += ” <WarningLevel>4</WarningLevel>r
n”
$projContent += ” </PropertyGroup>r
n”
$projContent += ” <ItemGroup>r
n”
$projContent += ” <Reference Include="Microsoft.CSharp
” />r
n”
$projContent += ” <Reference Include="System.ServiceModel
” />r
n”
$projContent += ” <Reference Include="System.Transactions
” />r
n”
$projContent += ” <Reference Include="System.Web.DynamicData
” />r
n”
$projContent += ” <Reference Include="System.Web.Entity
” />r
n”
$projContent += ” <Reference Include="System.Web.ApplicationServices
” />r
n”
$projContent += ” <Reference Include="System.ComponentModel.DataAnnotations
” />r
n”
$projContent += ” <Reference Include="System
” />r
n”
$projContent += ” <Reference Include="System.Data
” />r
n”
$projContent += ” <Reference Include="System.Core
” />r
n”
$projContent += ” <Reference Include="System.Data.DataSetExtensions
” />r
n”
$projContent += ” <Reference Include="System.Web.Extensions
” />r
n”
$projContent += ” <Reference Include="System.Xml.Linq
” />r
n”
$projContent += ” <Reference Include="System.Drawing
” />r
n”
$projContent += ” <Reference Include="System.Web
” />r
n”
$projContent += ” <Reference Include="System.Xml
” />r
n”
$projContent += ” <Reference Include="System.Configuration
” />r
n”
$projContent += ” <Reference Include="System.Web.Services
” />r
n”
$projContent += ” <Reference Include="System.EnterpriseServices
” />r
n”
$projContent += ” </ItemGroup>r
n”
$projContent += ” <PropertyGroup>r
n”
$projContent += ” <VisualStudioVersion Condition="'
$(VisualStudioVersion)’ == ”">10.0</VisualStudioVersion>
rn"
“‘
$projContent += " <VSToolsPath Condition=$(VSToolsPath)' == ''
“>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v
$(VisualStudioVersion)</VSToolsPath>r
n”
$projContent += ” </PropertyGroup>r
n”
$projContent += ” <Import Project="
$(MSBuildBinPath)\Microsoft.CSharp.targets" />
rn"
“
$projContent += " <Import Project=$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets
” Condition="'
$(VSToolsPath)’ != ”" />
rn"
“
$projContent += " <Import Project=$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets
” Condition="false
” />r
n”
$projContent += ” <ProjectExtensions>r
n”
$projContent += ” <VisualStudio>r
n”
$projContent += ” <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}
“>r
n”
$projContent += ” <WebProjectProperties>r
n”
$projContent += ” <UseIIS>False</UseIIS>r
n”
$projContent += ” <AutoAssignPort>True</AutoAssignPort>r
n”
$projContent += ” <DevelopmentServerPort>58060</DevelopmentServerPort>r
n”
$projContent += ” <DevelopmentServerVPath>/</DevelopmentServerVPath>r
n”
$projContent += ” <IISUrl>r
n”
$projContent += ” </IISUrl>r
n”
$projContent += ” <NTLMAuthentication>False</NTLMAuthentication>r
n”
$projContent += ” <UseCustomServer>True</UseCustomServer>r
n”
$projContent += ” <CustomServerUrl>http://localhost/</CustomServerUrl>r
n”
$projContent += ” <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>r
n”
$projContent += ” </WebProjectProperties>r
n”
$projContent += ” </FlavorProperties>r
n”
$projContent += ” </VisualStudio>r
n”
$projContent += ” </ProjectExtensions>r
n”
$projContent += ” <!– To modify your build process, add your task inside one of the targets below and uncomment it. r
n”
$projContent += ” Other similar extension points exist, see Microsoft.Common.targets.r
n”
$projContent += ” <Target Name="BeforeBuild
“>r
n”
$projContent += ” </Target>r
n”
$projContent += ” <Target Name="AfterBuild
“>r
n”
$projContent += ” </Target>r
n”
$projContent += ” –>r
n”
$projContent += “</Project>”
# Save the proj file
$projContent | Out-File ([System.IO.Path]::Combine(“Template”, “MyExtension.csproj”)) -Encoding “UTF8” -NoNewline
# Create the template zip file
[System.IO.Directory]::CreateDirectory(“Extension\ProjectTemplates\CSharp\Web\1033”)
[System.IO.Compression.ZipFile]::CreateFromDirectory(“Template”, “Extension\ProjectTemplates\CSharp\Web\1033\MyExtension.zip”)
# Create a content types xml file (an error will be thrown if this does not exist)
$conentTypesContent = “<?xml version="1.0
” encoding="utf-8
“?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types
“><Default Extension="vsixmanifest
” ContentType="text/xml
” /><Default Extension="zip
” ContentType="application/zip
” /></Types>”
# Save the content types file
$conentTypesContent | Out-File -literalPath “Extension\[Content_Types].xml” -Encoding “UTF8” -NoNewline
# Now create an extension manifest for the visual studio template
$extensionContent = “<PackageManifest Version="2.0.0
” xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011
“>r
n”
$extensionContent += ” <Metadata>r
n”
$extensionContent += ” <Identity Id="MyExtension - 1
” Version="0.1.0
” Language="en-US
” Publisher="MyExtension.net Ltd
” />r
n”
$extensionContent += ” <DisplayName>MyExtension Project Template</DisplayName>r
n”
$extensionContent += ” <Description xml:space="preserve
“>MyExtension Project Template Extension</Description>r
n”
$extensionContent += ” </Metadata>r
n”
$extensionContent += ” <Installation>r
n”
$extensionContent += ” <InstallationTarget Id="Microsoft.VisualStudio.Community
” Version="[14.0]
” />r
n”
$extensionContent += ” </Installation>r
n”
$extensionContent += ” <Dependencies>r
n”
$extensionContent += ” <Dependency Id="Microsoft.Framework.NDP
” DisplayName="Microsoft .NET Framework
” Version="[4.5,)
” />r
n”
$extensionContent += ” </Dependencies>r
n”
$extensionContent += ” <Assets>r
n”
$extensionContent += ” <Asset Type="Microsoft.VisualStudio.ProjectTemplate
” Path="ProjectTemplates
” />r
n”
$extensionContent += ” </Assets>r
n”
$extensionContent += “</PackageManifest>”
# Save the extension file
$extensionContent | Out-File “Extension\extension.vsixmanifest” -Encoding “UTF8” -NoNewline
# Create the extension zip file
[System.IO.Compression.ZipFile]::CreateFromDirectory(“Extension”, “MyExtension.vsix”)
# Delete the temporary directories
[System.IO.Directory]::Delete(“Extension”, $true)
[System.IO.Directory]::Delete(“Template”, $true)
When I run the script it successfully produces a MyExtension.vsix file. When I execute that file it installs the extension but if I now open Visual Studio 2015 and create a new project the project template (C# -> Web) doesn’t exist.
However if you do the following steps then it works fine (make sure you have uninstalled the old extension first):
- Rename the MyExtension.vsix file to MyExtension.zip and then extract to a directory
- Go into that directory and select all the files, right click and select Send to Compressed Folder
- Rename the generated zip file to MyExtension.vsix
- Execute this file to install the extension
It’s taken me hours to even notice this scenario fixes the issue. However this is a hack and in the long run it’s going to become quite tedious. I was wondering if anyone knew how my PowerShell script can be changed to fix this.
Thanks
Answer:
For some reason, [System.IO.Compression.ZipFile]::CreateFromDirectory will not create a zip/vsix file that works correctly,
even though it will show as installed. The template does not show in the new project UI.
Use 7zip instead to create zip files.
While I tried to investigate this issue, I did not like that the code was not using fully qualified paths, and the strings were hard to look at. I refactored your code a bit.
Based on my testing, this now works as expected.
CODE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
<# http://stackoverflow.com/questions/40462544/powershell-script-to-create-visual-studio-project-template-extension-zip-issue For some reason, [System.IO.Compression.ZipFile]::CreateFromDirectory will not create a zip/vsix file that works correctly, even though it will show as installed. The template does not show in the new project UI. Use 7zip instead to create zip files. #> Set-StrictMode -Version Latest $VerbosePreference = [System.Management.Automation.ActionPreference]::Continue # Makes debugging from ISE easier. if ($PSScriptRoot -eq "") { $root = Split-Path -Parent $psISE.CurrentFile.FullPath } else { $root = $PSScriptRoot } Set-Location $root <# Create a zip file with items under Path in the root of the zip file. #> function New-ZipFile([string]$Path, [string]$FileName) { $zipExe = 'C:\Program Files\7-Zip\7z.exe' $currentLocation = Get-Location Set-Location $Path & $zipExe a -tzip $FileName * -r Set-Location $currentLocation } # Create temporary directories for the zip archives "Extension", "Template" | % {New-Item (Join-Path $root $_) -ItemType Directory} # Build up the contents of the template file $templateContent = @' '@ # Save the template file $templateContent | Out-File (Join-Path $root "Template\MyExtension.vstemplate") -Encoding "UTF8" #-NoNewline # Build up the contents of the proj file $projContent = @' |
lt;/RootNamespace>
<AssemblyName>$safeprojectname
1 |
lt;/AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<UseIISExpress>false</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
</PropertyGroup>
<PropertyGroup Condition=” ‘$(Configuration)|$(Platform)’ == ‘Debug|AnyCPU’ “>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=” ‘$(Configuration)|$(Platform)’ == ‘Release|AnyCPU’ “>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include=”Microsoft.CSharp” />
<Reference Include=”System.ServiceModel” />
<Reference Include=”System.Transactions” />
<Reference Include=”System.Web.DynamicData” />
<Reference Include=”System.Web.Entity” />
<Reference Include=”System.Web.ApplicationServices” />
<Reference Include=”System.ComponentModel.DataAnnotations” />
<Reference Include=”System” />
<Reference Include=”System.Data” />
<Reference Include=”System.Core” />
<Reference Include=”System.Data.DataSetExtensions” />
<Reference Include=”System.Web.Extensions” />
<Reference Include=”System.Xml.Linq” />
<Reference Include=”System.Drawing” />
<Reference Include=”System.Web” />
<Reference Include=”System.Xml” />
<Reference Include=”System.Configuration” />
<Reference Include=”System.Web.Services” />
<Reference Include=”System.EnterpriseServices” />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition=”‘$(VisualStudioVersion)’ == ””>10.0</VisualStudioVersion>
<VSToolsPath Condition=”‘$(VSToolsPath)’ == ””>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project=”$(MSBuildBinPath)\Microsoft.CSharp.targets” />
<Import Project=”$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets” Condition=”‘$(VSToolsPath)’ != ”” />
<Import Project=”$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets” Condition=”false” />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID=”{349c5851-65df-11da-9384-00065b846f21}”>
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>58060</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://localhost/</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<!– To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name=”BeforeBuild”>
</Target>
<Target Name=”AfterBuild”>
</Target>
–>
</Project>
‘@
# Save the proj file
$projContent | Out-File (Join-Path $root “Template\MyExtension.csproj”) -Encoding “UTF8” #-NoNewline
# Create the template zip file
New-Item (Join-Path $root “Extension\ProjectTemplates\CSharp\Web\1033”) -ItemType Directory
New-ZipFile (Join-Path $root “Template”) (Join-Path $root “Extension\ProjectTemplates\CSharp\Web\1033\MyExtension.zip”)
# Create a content types xml file (an error will be thrown if this does not exist)
$conentTypesContent = @’
<?xml version=”1.0″ encoding=”utf-8″?><Types xmlns=”http://schemas.openxmlformats.org/package/2006/content-types”><Default Extension=”vsixmanifest” ContentType=”text/xml” /><Default Extension=”zip” ContentType=”application/zip” /></Types>
‘@
# Save the content types file
$conentTypesContent | Out-File -literalPath (Join-Path $root “Extension\[Content_Types].xml”) -Encoding “UTF8″ #-NoNewline
# Now create an extension manifest for the visual studio template
$extensionContent = @’
<PackageManifest Version=”2.0.0″ xmlns=”http://schemas.microsoft.com/developer/vsx-schema/2011″>
<Metadata>
<Identity Id=”MyExtension – 1″ Version=”0.1.0″ Language=”en-US” Publisher=”MyExtension.net Ltd” />
<DisplayName>MyExtension Project Template</DisplayName>
<Description xml:space=”preserve”>MyExtension Project Template Extension</Description>
</Metadata>
<Installation>
<InstallationTarget Id=”Microsoft.VisualStudio.Community” Version=”[14.0]” />
</Installation>
<Dependencies>
<Dependency Id=”Microsoft.Framework.NDP” DisplayName=”Microsoft .NET Framework” Version=”[4.5,)” />
</Dependencies>
<Assets>
<Asset Type=”Microsoft.VisualStudio.ProjectTemplate” Path=”ProjectTemplates” />
</Assets>
</PackageManifest>
‘@
# Save the extension file
$extensionContent | Out-File (Join-Path $root “Extension\extension.vsixmanifest”) -Encoding “UTF8” #-NoNewline
# Create the extension zip file
New-ZipFile (Join-Path $root “Extension”) (Join-Path $root “MyExtension.vsix”)
# Delete the temporary directories
“Extension”, “Template” | % {Remove-Item (Join-Path $root $_) -Recurse -Force}