Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell Script to Create Visual Studio Project Template Extension Zip Issue

Tags:

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:

# 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 = "<?xml version=`"1.0`" encoding=`"utf-8`"?>`r`n"
$templateContent += "<VSTemplate Version=`"3.0.0`" Type=`"Project`" xmlns=`"http://schemas.microsoft.com/developer/vstemplate/2005`" xmlns:sdk=`"http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010`">`r`n"
$templateContent += "    <TemplateData>`r`n"
$templateContent += "        <Name>MyExtension</Name>`r`n"
$templateContent += "        <Description>MyExtension</Description>`r`n"
$templateContent += "        <Icon>MyExtension.ico</Icon>`r`n"
$templateContent += "        <ProjectType>CSharp</ProjectType>`r`n"
$templateContent += "        <ProjectSubType></ProjectSubType>`r`n"
$templateContent += "        <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>`r`n"
$templateContent += "        <SortOrder>1000</SortOrder>`r`n"
$templateContent += "        <TemplateID>61251892-9605-4816-846b-858352383c38</TemplateID>`r`n"
$templateContent += "        <CreateNewFolder>true</CreateNewFolder>`r`n"
$templateContent += "        <DefaultName>MyExtension</DefaultName>`r`n"
$templateContent += "        <ProvideDefaultName>true</ProvideDefaultName>`r`n"
$templateContent += "    </TemplateData>`r`n"
$templateContent += "    <TemplateContent>`r`n"
$templateContent += "        <Project File=`"MyExtension.csproj`" ReplaceParameters=`"true`"></Project>`r`n"
$templateContent += "    </TemplateContent>`r`n"
$templateContent += "</VSTemplate>"

# 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 = "<?xml version=`"1.0`" encoding=`"utf-8`"?>`r`n"
$projContent += "<Project ToolsVersion=`"4.0`" DefaultTargets=`"Build`" xmlns=`"http://schemas.microsoft.com/developer/msbuild/2003`">`r`n"
$projContent += "  <Import Project=`"`$(MSBuildExtensionsPath)\`$(MSBuildToolsVersion)\Microsoft.Common.props`" Condition=`"Exists('`$(MSBuildExtensionsPath)\`$(MSBuildToolsVersion)\Microsoft.Common.props')`" />`r`n"
$projContent += "  <PropertyGroup>`r`n"
$projContent += "    <Configuration Condition=`" '`$(Configuration)' == '' `">Debug</Configuration>`r`n"
$projContent += "    <Platform Condition=`" '`$(Platform)' == '' `">AnyCPU</Platform>`r`n"
$projContent += "    <ProductVersion>`r`n"
$projContent += "    </ProductVersion>`r`n"
$projContent += "    <SchemaVersion>2.0</SchemaVersion>`r`n"
$projContent += "    <ProjectGuid>{403C08FA-9E44-4A8A-A757-1662142E1334}</ProjectGuid>`r`n"
$projContent += "    <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>`r`n"
$projContent += "    <OutputType>Library</OutputType>`r`n"
$projContent += "    <AppDesignerFolder>Properties</AppDesignerFolder>`r`n"
$projContent += "    <RootNamespace>`$safeprojectname`$</RootNamespace>`r`n"
$projContent += "    <AssemblyName>`$safeprojectname`$</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>`r`n"
$projContent += "    <VSToolsPath Condition=`"'`$(VSToolsPath)' == ''`">`$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v`$(VisualStudioVersion)</VSToolsPath>`r`n"
$projContent += "  </PropertyGroup>`r`n"
$projContent += "  <Import Project=`"`$(MSBuildBinPath)\Microsoft.CSharp.targets`" />`r`n"
$projContent += "  <Import Project=`"`$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets`" Condition=`"'`$(VSToolsPath)' != ''`" />`r`n"
$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):

  1. Rename the MyExtension.vsix file to MyExtension.zip and then extract to a directory
  2. Go into that directory and select all the files, right click and select Send to Compressed Folder
  3. Rename the generated zip file to MyExtension.vsix
  4. 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

like image 824
nfplee Avatar asked Nov 07 '16 10:11

nfplee


1 Answers

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

<#
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 = @'
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate Version="3.0.0" Type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010">
    <TemplateData>
        <Name>MyExtension</Name>
        <Description>MyExtension</Description>
        <Icon>MyExtension.ico</Icon>
        <ProjectType>CSharp</ProjectType>
        <ProjectSubType></ProjectSubType>
        <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
        <SortOrder>1000</SortOrder>
        <TemplateID>61251892-9605-4816-846b-858352383c38</TemplateID>
        <CreateNewFolder>true</CreateNewFolder>
        <DefaultName>MyExtension</DefaultName>
        <ProvideDefaultName>true</ProvideDefaultName>
    </TemplateData>
    <TemplateContent>
        <Project File="MyExtension.csproj" ReplaceParameters="true"></Project>
    </TemplateContent>
</VSTemplate>
'@

# 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 = @'
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
    <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>
    </ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{403C08FA-9E44-4A8A-A757-1662142E1334}</ProjectGuid>
    <ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>$safeprojectname$</RootNamespace>
    <AssemblyName>$safeprojectname$</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}
like image 198
Kory Gill Avatar answered Oct 08 '22 17:10

Kory Gill