Cloning an ASP.NET vNext Project with PowerShell

In my last article I did the hard work of copying my Base ASP.NET vNext project (which is better than an empty ASP.NET project but not as encumbered as an ASP.NET starter project). The next part of that process is to adjust certain files so that the project name is replaced with my project name.

The first thing I need to do is to get the project names so I can use them as replacement strings. To do this, I use Split-Path -Leaf, like this:

$SrcPath = Resolve-Path -Path $Source
$DstPath = Resolve-Path -Path $Destination
$SrcProject = Split-Path -Leaf $SrcPath.Path
$DstProject = Split-Path -Leaf $DstPath.Path

I could have done this in two lines, but readability is important some times. Next I’m going to get a list of files that match my criteria:

$prjFiles = Get-ChildItem -Path $DstPath.Path -File -Recurse | 
    Select-String -SimpleMatch -Pattern $SrcProject

Select-String is the PowerShell equivalent of grep. The resulting objects contain the file name, line number and string that matches.

To effect the change I need to do an in-place edit. To do that, I need the following:

        foreach ($file in $prjFiles) {
            Write-Verbose "[Copy-VSProject] Updating project reference in $($file.Path)"
            (Get-Content $file.Path) | %{ $_ -replace $SrcProject,$DstProject } | Set-Content $file.Path

I have to wrap Get-Content in it’s own pipeline (which is why it is surrounded in parentheses) because otherwise I can’t use Set-Content – the file is locked for reading, so I can’t write to it. Executing Get-Content in the sub-pipeline ensures it is closed before the Set-Content is called.

The resulting function looks like this.

   Clone an ASP.NET vNext Project with a new name
   Takes an existing ASP.NET vNext Project and clones it into
   a new project directory, changing the name of the project in
   all the right files.
   A simple example of cloning a base project into MyProject

   Copy-VSProject -Source .\BaseProject -Destination .\MyProject
   Don't copy node_modules and jspm_modules to the target

   Copy-VSProject -Source .\BaseProject -Destination .\MyProject -Exclude @("node_modules","jspm_modules")
   The project name is obtained from the directory name.  Both
   Source and Destination must be directories.  Internal references
   to the destination project are updated.
function Copy-VSProject
        # The source project.  This should be a directory containing
        # a project.json file.
        [ValidateScript({Test-Path $_ -PathType "Container"})]

        # The destination project.  This should be a directory name
        # that does not exist.
        [ValidateScript({!(Test-Path $_)})]

        # Ignore a set of directories (and their contents).  This can be
        # used to not copy common build directories.  The default set is
        # node_modules, bower_components, bin, obj
        $Exclude = @( "node_modules", "bower_components", "bin", "obj" )

        Write-Verbose "[Copy-VSProject] Checking that $Source is on the FileSystem"
        $SrcPath = Resolve-Path -Path $Source
        if ($SrcPath.Provider.Name -ne "FileSystem") {
            throw "Source must be on a FileSystem"

        Write-Verbose "[Copy-VSProject] Initiating Clone of Project $($SrcPath.Path)"

        Write-Verbose "[Copy-VSProject] Copying Files... (Exclude Length = $($Exclude.Length))"
        # Note that -Recurse and -Exclude do not work together on Copy-Item
        # See
        # This is the workaround - first get the list of files
        if ($Exclude.length -gt 0) {
            $regexp = "(" + (($Exclude | %{ "\\$_\\" }) -join "|") + ")"
            Write-Verbose "[Copy-VSProject] Excluding via regular expression: $regexp"
            $files = (Get-ChildItem -Path $SrcPath.Path -Recurse -ErrorAction SilentlyContinue | ? {
                $_.FullName.Substring($SrcPath.Path.Length) -notmatch $regexp
        } else {
            $files = Get-ChildItem -Path $SrcPath.Path -Recurse -ErrorAction SilentlyContinue
        Write-Verbose "[Copy-VSProject] Found $($files.Length) files and directories to copy"
        Write-Debug "Continue with copying process?"

        # Then copy the files to the new location
        $files | Foreach-Object { 
            $target = Join-Path $Destination $_.FullName.Substring($SrcPath.Path.Length)
            Write-Verbose "Copying $($_.FullName) to $target"
            Copy-Item -Path $_.FullName -Destination $target 
        Write-Verbose "[Copy-VSProject] Finished Copying Files..."

        # Convert the destination to an absolute path
        $DestPath = Resolve-Path -Path $Destination
        Write-Verbose "Destination Path is $($DestPath.Path)"

        # Extract the project names
        $SrcProject = Split-Path -Leaf $SrcPath.Path
        $DstProject = Split-Path -Leaf $DestPath.Path
        Write-Verbose "[Copy-VSProject] Convert Project `"$SrcProject`" into Project `"$DstProject`""
        $prjFiles = Get-ChildItem -Path $DestPath.Path -File -Recurse | Select-String -SimpleMatch -Pattern $SrcProject
        Write-Verbose "[Copy-VSProject] Found $($prjFiles.Length) files with a reference to source project"
        Write-Debug "Continue with Conversion process?"
        foreach ($file in $prjFiles) {
            Write-Verbose "[Copy-VSProject] Updating project reference in $($file.Path)"
            (Get-Content $file.Path) | %{ $_ -replace $SrcProject,$DstProject } | Set-Content $file.Path
        Write-Verbose "[Copy-VSProject] Clone of Project $($SrcPath.Path) is complete"

I started by editing this in the PowerShell ISE. Under the Edit Menu there is a Start Snippets selection and in there is an Advanced Cmdlet snippet that has a lot of the boiler plate. You can recognize the rest of the script (aside from the verbose output) from the snippets I posted above and yesterday. Note the Write-Debug lines. If you add on a -Debug parameter to the call then the script will pause at those steps and wait for you to confirm the operation. I’ve done this before I actually copy and before I do the changes. Strictly speaking, this is a PowerShell v4 functionality. I should probably use -WhatIf to show what I am doing but not do it. Similarly, I should use -Confirm to suggest I need to confirm the decision. These are things for future revisions.

Making a PowerShell Module

I’m going to make a module called VSUtils. The first thing I do is create a module directory. Personal modules go in $HOME\Documents\WindowsPowerShell\Modules. You can get the actual paths that PowerShell looks with this simple piece of PowerShell:

$env:PSModulePath -split ";"

The first one listed is likely to be the right one. Create a directory called VSUtils in that directory and copy the script file into that directory as VSUtils.psm1.

Now that I have the script file in the right place I need to create a module manifest. There is a cmdlet for that. Firstly, let’s get the help:

Get-Help New-ModuleManifest -ShowWindow

There is some great help but it’s a little confusing. There are some things you want to specify – like the functions to export and the root module name. Other things are optional. Here is the version that created my module manifest:

New-ModuleManifest -Path .\VSUtils.psd1 `
    -Author 'Adrian Hall' -PowerShellVersion 3.0 `
    -FunctionsToExport Copy-VSProject -RootModule VSUtils

You should now be able to run Get-Command to find your module:


Using the PowerShell Module

To create a new project, open up your PowerShell prompt and type the following:

$Base = "~\Source\GitHub\blog-code\BaseAspNetApplication"
cd ~\Documents\Projects
Copy-VSProject -Source $Base -Destination .\MyNewProject

The variable $Base is set to the location of my BaseAspNetApplication. I could just type it, I guess.

Now for the magic. Open up Visual Studio 2015. Select File -> Open Project… and find your newly created folder. Select the project.json file within that folder. Visual Studio 2015 will create the project on the fly for you.

You will have to right click on the Dependencies nodes and select Restore Packages since I explicitly excluded those from the copy in my Copy-VSProject script. However, everything else should “just work”.

Now you can set up projects with your scaffolding easily and use PowerShell to clone the scaffolding to create new projects.