Microsoft.PowerShell.NanoServer.SDK.psm1


Set-StrictMode -Version Latest

$Script:DotNetCoreRefAsmPath = "${env:SystemDrive}\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore"
$Script:NanoPSRefAsmInfoFile = "NanoPSReferenceAssemblyInfo.xml"
$Script:HasAdminPrevilege    = $null
$Script:CmdletSnippet = @"
using System;
using System.Management.Automation;
 
namespace {0}
{{
    [Cmdlet("Verb", "Noun")]
    [Alias("Alias")]
    public class Class1 : PSCmdlet
    {{
        [Parameter(Position = 0, ValueFromPipeline = true)]
        public string Param
        {{
            get; set;
        }}
 
        protected override void BeginProcessing()
        {{
            base.BeginProcessing();
        }}
 
        protected override void ProcessRecord()
        {{
            base.ProcessRecord();
        }}
 
        protected override void EndProcessing()
        {{
            base.EndProcessing();
        }}
    }}
}}
"@


## Default Culture is 'en-US'
data LocalizedData
{
    ConvertFrom-StringData -StringData @'
        NeedToRunInVisualStudio=This cmdlet should only be run in the Package Manager Console in Visual Studio 2015. To open the Package Manager Console, click the 'Tools' menu, select 'NuGet Package Manager', and then click 'Package Manager Console'. {0}
        RequireVisualStudio2015=This cmdlet requires Visual Studio 2015. {0}
        InstallVisualStudio2015=If you don't have Visual Studio 2015 installed, you can download and install Visual Studio Community 2015 from https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx. Make sure you select to install the "Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK" feature during Visual Studio setup.
        NeedAdminPrevilegeMessage=A PowerShell process will be started requesting Administrator privileges to copy the CoreCLR and PowerShell Core reference assemblies for Windows Server 2016 Nano Server to the Visual Studio reference assembly folder '{0}'. Do you want to continue?
        NeedAdminPrevilegeCaption=Nano Server PowerShell SDK Initial Setup
        InitialSetupCancelled=Initial setup for the SDK was canceled. {0}
        UnknownDTEFailure=An operation in Visual Studio failed with the error '{0}'. {1}
        NoCurrentOpenSolution=No Visual Studio solution is currently open.
        ProjectAlreadyExistsInCurrentSolution=The current Visual Studio solution already has a project named '{0}'.
        StartProcessExecutionFailed=Failed to copy the CoreCLR and PowerShell Core reference assemblies for Windows Server 2016 Nano Server to the Visual Studio reference assembly folder. {0}
        StartProcessFailedToStart=Failed to copy the CoreCLR and PowerShell Core reference assemblies for Windows Server 2016 Nano Server due to the error '{0}'. {1}
        FollowManualSteps=Please try the command again, or you can run 'Show-SdkSetupReadMe' and follow the instructions for setting up the SDK and creating a Visual Studio C# project manually.
        AboutToDeployReferenceAssemblies=VERBOSE: Preparing to deploy the CoreCLR and PowerShell Core reference assemblies for Windows Server 2016 Nano Server to the Visual Studio reference assembly folder '{0}'
        DoneDeployingReferenceAssemblies=VERBOSE: CoreCLR and PowerShell Core reference assemblies deployed at '{0}'
        AboutToCreateCSharpProject=VERBOSE: Preparing to create the Visual Studio C# project '{0}'
        DoneCreatingCSharpProject=VERBOSE: The Visual Studio C# project '{0}' has been successfully created and is ready to use
        FetchProjectTemplate=VERBOSE: -- Try getting the Visual Studio C# project template 'Visual C#\\Windows\\Class Library'
        CreateProjectWithNewSolution=VERBOSE: -- Creating Visual Studio project with a new solution named '{0}'
        CreateProjectInCurrentSolution=VERBOSE: -- Creating Visual Studio project within the currently open solution '{0}'
        CloseCurrentlyOpenSolution=VERBOSE: -- Save and close the currently open Visual Studio solution '{0}'
        DeployTargetFilesToProject=VERBOSE: -- Copy MSBuild Targets files 'Microsoft.Coresys.Common.Targets' and 'Microsoft.CoreSys.CSharp.Targets' to the Visual Studio project folder '{0}'
        UpdateProjectFile=VERBOSE: -- Update the Visual Studio project file '{0}' to target the right .NET Framework version and import the new .Targets files
        PrerequisitesForRemoteDebugger=One or more prerequisites are missing for this cmdlet. Please make sure your Visual Studio 2015 is upgraded to 'Update 2' [or higher] and the "Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK" feature is installed.
        SessionNotOpened=The remote PSSession is not in 'Opened' state. Please make sure you have an opened PSSession targeting the remote Nano Server.
        CopyOneCoreDebuggerBits={0}-- Copy Nano Server remote debugger binaries to {1}
        CopyPlatformAgnosticBits={0}-- Copy platform-agnostic debugger binaries to {1}
        CopyCRTBits={0}-- Copy C Run-time (CRT) debugger binaries to {1}
        CopyLocalCoreCLRBits={0}-- Copy the isolated CoreCLR binaries that are solely required by the CoreCLR remote debugger to {1}
        CopyCoreCLRDebugBits={0}-- Copy the CoreCLR debugging binaries to {1}
        ConfigureRemoteDebugger={0}-- Configure the remote debugger and Windows Firewall settings
        CopyToNanoServer=Nano Server at '{0}'
        CannotCopyRemotely=The PowerShell version you are using is '{0}' and it does not support copying files over a remote PSSession. Therefore, a package of remote debugger binaries will be prepared locally that you can manually copy to the target Nano Server machine. To get a better automated experience, please install Windows Management Framework 5.0 or later, or upgrade to Windows 10.
        ManualInstallSteps=A package of remote debugger binaries has been prepared at '{0}' and the folder has been opened for you. To manually install the remote debugger on the target Nano Server machine, please copy the folder 'RemoteDebuggerPack' to the system drive of the target Nano Server machine, and then run the script 'install.ps1' from that folder in a remote PowerShell session targeting the machine.
'@

}

## Load localized strings
## In case localized resource is not available we revert back to English as defined in LocalizedData section so ignore the error instead of showing it to user.
Import-LocalizedData -BindingVariable LocalizedData -FileName NanoPowerShellSDK.Resource.psd1 -ErrorAction SilentlyContinue

#region "Utilities"

##
## Test if the current user has admin previlege
##
function Test-AdminPrevilege
{
    if ($null -eq $Script:HasAdminPrevilege)
    {
        $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $Principal = New-Object -TypeName System.Security.Principal.WindowsPrincipal -ArgumentList $Identity
        $Script:HasAdminPrevilege = $Principal.IsInRole("Administrators")
    }

    return $Script:HasAdminPrevilege
}

##
## Update the project file
##
function Update-ProjectFile
{
    param(
        [string] $ProjectFile,
        [string] $OutputType
    )

    $Xml = [xml] (Get-Content -Path $ProjectFile)

    @($Xml.Project.PropertyGroup)[0].TargetFrameworkVersion = "v0.1"
    @($Xml.Project.PropertyGroup)[0].OutputType = $OutputType
    @($Xml.Project.Import)[-1].Project = "Microsoft.CoreSys.CSharp.targets"
    
    ## Remove references
    $null = $Xml.Project.RemoveChild(@($Xml.Project.ItemGroup)[0])

    Save-Xml -XmlDoc $Xml -Path $ProjectFile
}

##
## Update the sample code in Class1.cs
##
function Update-SampleCode
{
    param(
        [string] $SampleFile,
        [ValidateSet("Library", "EXE")]
        [string] $OutputType
    )
    
    if ($OutputType -eq "Library")
    {
        $Namespace  = Get-Content -Path $SampleFile | ? { $_ -like "namespace *" } | % { ($_ -split ' ')[1] }
        $SampleCode = $Script:CmdletSnippet -f $Namespace

        ## Overwrite Class1.cs with sample cmdlet code
        [System.IO.File]::WriteAllText($SampleFile, $SampleCode, [System.Text.Encoding]::UTF8)
    }
}

##
## Save XmlDocument to the specified file
##
function Save-Xml
{
    param(
        [xml] $XmlDoc,
        [string] $Path
    )
    
    $XmlWriterSetting = New-Object -TypeName System.Xml.XmlWriterSettings
    $XmlWriterSetting.Indent = $true
    $XmlWriterSetting.IndentChars = " "
    $XmlWriterSetting.NewLineChars = "`r`n"
    $XmlWriterSetting.NewLineHandling = "Replace"

    try {
        $XmlWriter = [System.Xml.XmlWriter]::Create($Path, $XmlWriterSetting)
        $XmlDoc.Save($XmlWriter)
    } finally {
        $XmlWriter.Close()
    }
}

##
## Utility to write terminating error
##
function ThrowError
{
    param(
        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCmdlet]
        $CallerPSCmdlet,

        [parameter(Mandatory = $true)]
        [System.String]        
        $ExceptionName,

        [parameter(Mandatory = $true)]
        [System.String]
        $ExceptionMessage,
        
        [System.Object]
        $ExceptionObject,
        
        [parameter(Mandatory = $true)]
        [System.String]
        $ErrorId,

        [parameter(Mandatory = $true)]
        [System.Management.Automation.ErrorCategory]
        $ErrorCategory
    )
        
    $exception = New-Object $ExceptionName $ExceptionMessage;
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject    
    $CallerPSCmdlet.ThrowTerminatingError($errorRecord)
}

##
## Utility to wait the copying process with a pesudo progress bar
##
function Wait-WithPesudoProgressBar
{
    param(
        [parameter(Mandatory = $true)]
        [System.Diagnostics.Process] $Process
    )

    $Count = 10
    $Activity = 'NanoServer PowerShell SDK Initial Setup'
    $Status = 'Copy NanoServer PowerShell Reference Assemblies'

    Write-Progress -Activity $Activity -Status $Status -PercentComplete $Count
    while (-not $Process.HasExited)
    {
        Start-Sleep -Milliseconds 150
        if ($Count -lt 99)
        {
            $Count = $Count + (100 - $Count) / 10
        }
        Write-Progress -Activity $Activity -Status $Status -PercentComplete $Count
    }
    Write-Progress -Activity $Activity -Status $Status -Completed
}

#endregion "Utilities"

##
## Create a new CSharp project targeting CoreCLR used by Nano PowerShell
##
function New-NanoCSharpProject
{
    <#
      .SYNOPSIS
      Creates a new Visual Studio C# project targeting CoreCLR and PowerShell Core included in the Windows Server 2016 Nano Server.
 
      .DESCRIPTION
      This cmdlet helps you to create Visual Studio C# projects targeting CoreCLR and PowerShell Core included in the Windows Server 2016 Nano Server.
         - You can choose to create a new project in a new solution, or add a new project to the currently open solution.
         - You can choose to have the project output either a DLL library or EXE program.
 
      NOTE: Please make sure the following Visual Studio prerequisites are installed prior to using the cmdlets:
         - Visual Studio 2015 Update 2 [or higher]
         - Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK
 
      This cmdlet should be run in the Package Manager Console in Visual Studio 2015.
         - If you don't have Visual Studio 2015 installed, you can install Visual Studio Community 2015 from
           https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx
 
      This cmdlet first checks if initial setup of the SDK is done. If not, it will copy the reference assemblies to the reference assembly folder of Visual Studio 2015.
 
      .EXAMPLE
      PS C:\> New-NanoCSharpProject -Path D:\Projects -ProjectName DISMCmdlet -SolutionName DISM -OutputType Library
 
      This command creates a new C# project in a new solution targeting CoreCLR and PowerShell Core in Nano Server. The new project produces a DLL.
 
      .EXAMPLE
      PS C:\> New-NanoCSharpProject -Path D:\Temp -ProjectName Project2 -AddToCurrentSolution -OutputType EXE -Verbose
 
      This command creates a new C# project in the currently open solution targeting CoreCLR and PowerShell Core in Nano Server. The new project produces an EXE. The Verbose parameter displays verbose messages in the output.
 
      .PARAMETER Path
      Specifies the destination path for the new Visual Studio C# project.
 
      .PARAMETER ProjectName
      Specifies the name of the new Visual Studio C# project.
 
      .PARAMETER SolutionName
      Specifies the name of the new Visual Studio solution.
 
      .PARAMETER OutputType
      Specifies the output file type of the new Visual Studio project. Valid values are Library and EXE.
 
      .PARAMETER AddToCurrentSolution
      Creates the new Visual Studio project in the currently open solution.
    #>


    [CmdletBinding(DefaultParameterSetName = "NewSolution")]
    [Alias("nproj")]
    param(
        [parameter(Mandatory=$true, Position = 0)]
        [string] $Path,

        [parameter(Mandatory=$true, Position = 1)]
        [string] $ProjectName,
        
        [parameter(ParameterSetName = "NewSolution", Position = 2)]
        [ValidateNotNullOrEmpty()]
        [string] $SolutionName,

        [Parameter(Position = 3)]
        [ValidateSet("Library", "EXE")]
        [string] $OutputType = "Library",

        [parameter(ParameterSetName = "CurrentSolution")]
        [switch] $AddToCurrentSolution
    )

    ##
    ## Cmdlet needs to run in Package Management Console in Visual Studio
    ##
    if ($Host.Name -ne "Package Manager Host")
    {
        $Message = $LocalizedData.NeedToRunInVisualStudio -f $LocalizedData.InstallVisualStudio2015
        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId "SupportedInPackageManagerConsoleOnly" `
                   -CallerPSCmdlet $PSCmdlet `
                   -ExceptionObject $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }

    ##
    ## Visual Studio 2015 is required for authoring/debugging CoreCLR code
    ##
    if ($DTE.Name -ne "Microsoft Visual Studio" -or $DTE.Version -ne "14.0")
    {
        $Message = $LocalizedData.RequireVisualStudio2015 -f $LocalizedData.InstallVisualStudio2015
        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId "RequireVisualStudio2015" `
                   -CallerPSCmdlet $PSCmdlet `
                   -ExceptionObject $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }

    ##
    ## Do initial setup if needed
    ##
    $CoreCLRRefPath = Join-Path -Path $Script:DotNetCoreRefAsmPath -ChildPath v0.1
    $RefAsmInfoPath = Join-Path -Path $CoreCLRRefPath -ChildPath $Script:NanoPSRefAsmInfoFile
    $ModuleBasePath = $PSCmdlet.MyInvocation.MyCommand.Module.ModuleBase

    $RequireInitialSetup = -not (Test-Path -Path $RefAsmInfoPath -PathType Leaf)
    if (-not $RequireInitialSetup)
    {
        $SDKVersion = $null
        try { $SDKVersion = Import-Clixml -Path $RefAsmInfoPath } catch { }
        
        if ($SDKVersion -eq $null -or $SDKVersion.NanoPS -ne 'RTM') {
            $RequireInitialSetup = $true
        }
    }

    if ($RequireInitialSetup)
    {
        $Message = $LocalizedData.AboutToDeployReferenceAssemblies -f $Script:DotNetCoreRefAsmPath
        Write-Verbose -Message $Message

        $SourceRefPath = Join-Path -Path $ModuleBasePath -ChildPath SDK\v0.1
        $ScriptToRun = @'
 
            $PreferenceCopy = $ErrorActionPreference
            try {{
                $ErrorActionPreference = "Stop"
 
                $WriteProgress = $Host.Name -eq 'Package Manager Host'
                $Activity = 'NanoServer PowerShell SDK Initial Setup'
 
                if (-not (Test-Path -Path '{0}' -PathType Container)) {{
                    ## Target 'v0.1' folder not exists
                    $null = New-Item -Path '{0}' -ItemType Directory -Force
                }} else {{
                    ## Remove old reference assemblies from 'v0.1' folder
                    if ($WriteProgress) {{
                        $Status = 'Remove Old CoreCLR Reference Assemblies'
                        $ItemsToRemove = @(Get-ChildItem -Path '{0}')
                        $ItemCount = $ItemsToRemove.Count
                     
                        for ($Index = 0; $Index -lt $ItemCount; $Index++) {{
                            Write-Progress -Activity $Activity -Status $Status -PercentComplete ($Index/$ItemCount*100)
                            Remove-Item -Path $ItemsToRemove[$Index].FullName -Recurse -Force
                        }}
                        Write-Progress -Activity $Activity -Status $Status -Completed
                    }} else {{
                        Remove-Item -Path '{0}\*' -Recurse -Force
                    }}
                }}
 
                ## Copy reference assemblies to 'v0.1' folder
                if ($WriteProgress) {{
                    $Status = 'Copy NanoServer PowerShell Reference Assemblies'
                    $ItemsToCopy = @(Get-ChildItem -Path '{1}')
                    $ItemCount = $ItemsToCopy.Count
 
                    for ($Index = 0; $Index -lt $ItemCount; $Index++) {{
                        Write-Progress -Activity $Activity -Status $Status -PercentComplete ($Index/$ItemCount*100)
                        Copy-Item -Path $ItemsToCopy[$Index].FullName -Destination '{0}' -Recurse -Force
                    }}
                    Write-Progress -Activity $Activity -Status $Status -Completed
                }} else {{
                    Copy-Item -Path '{1}\*' -Destination '{0}' -Recurse -Force
                }}
 
                ## Write the info file
                Remove-Item -Path '{2}' -Recurse -Force -ErrorAction SilentlyContinue
                [pscustomobject]@{{ NanoPS = 'RTM' }} | Export-Clixml -Path '{2}' -Force
                Get-Item '{2}' | % {{ $_.Attributes = [System.IO.FileAttributes]::Hidden }}
 
            }} finally {{
                $ErrorActionPreference = $PreferenceCopy
            }}
 
'@
 -f $CoreCLRRefPath, $SourceRefPath, $RefAsmInfoPath

        if (Test-AdminPrevilege)
        {
            $ScriptBlockToRun = [scriptblock]::Create($ScriptToRun)
            & $ScriptBlockToRun
        }
        else
        {
            $Message  = $LocalizedData.NeedAdminPrevilegeMessage -f $Script:DotNetCoreRefAsmPath
            $Caption  = $LocalizedData.NeedAdminPrevilegeCaption
            if (-not $PSCmdlet.ShouldContinue($Message, $Caption))
            {
                $Message = $LocalizedData.InitialSetupCancelled -f $LocalizedData.FollowManualSteps
                Write-Warning -Message $Message

                return
            }
            
            $ByteArray = [System.Text.Encoding]::Unicode.GetBytes($ScriptToRun)
            $Base64EncodedScript = [System.Convert]::ToBase64String($ByteArray)

            try {
                $proc = Start-Process powershell -ArgumentList "-EncodedCommand $Base64EncodedScript" -Verb RunAs -WindowStyle Hidden -PassThru
                Wait-WithPesudoProgressBar -Process $proc

                if ($proc.ExitCode -ne 0) {
                    $Message = $LocalizedData.StartProcessExecutionFailed -f $LocalizedData.FollowManualSteps
                    ThrowError -ExceptionName "System.InvalidOperationException" `
                               -ExceptionMessage $Message `
                               -ErrorId "FailedToCopyReferenceAssemblies" `
                               -CallerPSCmdlet $PSCmdlet `
                               -ErrorCategory InvalidOperation
                }
            } catch {
                $Message = $LocalizedData.StartProcessFailedToStart -f $_.Exception.Message, $LocalizedData.FollowManualSteps
                ThrowError -ExceptionName "System.InvalidOperationException" `
                           -ExceptionMessage $Message `
                           -ErrorId "FailedToStartAWorkingProcess" `
                           -CallerPSCmdlet $PSCmdlet `
                           -ErrorCategory InvalidOperation `
            }
        }

        $Message = $LocalizedData.DoneDeployingReferenceAssemblies -f $CoreCLRRefPath
        Write-Verbose -Message $Message
    }

    ##
    ## Create the NanoPS CSharp Project
    ##
    $Message = $LocalizedData.AboutToCreateCSharpProject -f $ProjectName
    Write-Verbose -Message $Message

    try {
        ## Fetch the project template
        Write-Verbose -Message $LocalizedData.FetchProjectTemplate
        $TemplatePath = $DTE.Solution.GetProjectTemplate("Windows\Class Library", "CSharp")
        
        ## Create the project
        if ($PSCmdlet.ParameterSetName -eq "NewSolution")
        {
            if (-not $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("SolutionName"))
            {
                $SolutionName = $ProjectName
            }

            $Message = $LocalizedData.CreateProjectWithNewSolution -f $SolutionName
            Write-Verbose -Message $Message

            $SolutionDir = Join-Path -Path $Path -ChildPath $SolutionName
            $ProjectDir  = Join-Path -Path $SolutionDir -ChildPath $ProjectName
            if (-not (Test-Path -Path $SolutionDir -PathType Container))
            {
                $null = New-Item -Path $SolutionDir -ItemType Directory -Force
            }

            ## If a solution is currently open, close it first
            if ($DTE.Solution.IsOpen)
            {
                $Message = $LocalizedData.CloseCurrentlyOpenSolution -f ($DTE.Solution.Properties.Item("Name").Value)
                Write-Verbose -Message $Message

                $DTE.Solution.Close($true)
            }

            ## Create the new solution
            $DTE.Solution.Create($SolutionDir, $SolutionName)
            $SolutionFilePath = $DTE.Solution.Properties.Item("Path").Value
            $DTE.Solution.SaveAs($SolutionFilePath)
        }
        else
        {
            $Message = $LocalizedData.CreateProjectInCurrentSolution -f ($DTE.Solution.Properties.Item("Name").Value)
            Write-Verbose -Message $Message

            ## ParameterSet is 'CurrentSolution', but no solution is currently open
            if (-not $DTE.Solution.IsOpen)
            {
                $Message = $LocalizedData.NoCurrentOpenSolution
                ThrowError -ExceptionName "System.InvalidOperationException" `
                           -ExceptionMessage $Message `
                           -ErrorId "NoExistingOpenSolution" `
                           -CallerPSCmdlet $PSCmdlet `
                           -ErrorCategory InvalidOperation
            }

            $ExistingProjects = $DTE.Solution.Projects | % ProjectName
            if ($ProjectName -in $ExistingProjects)
            {
                $Message = $LocalizedData.ProjectAlreadyExistsInCurrentSolution -f $ProjectName
                ThrowError -ExceptionName "System.ArgumentException" `
                           -ExceptionMessage $Message `
                           -ErrorId "ProjectAlreadyExists" `
                           -CallerPSCmdlet $PSCmdlet `
                           -ErrorCategory InvalidOperation `
                           -ExceptionObject $ProjectName
            }

            $ProjectDir = Join-Path -Path $Path -ChildPath $ProjectName
        }

        ## Create the template project
        $DTE.Solution.AddFromTemplate($TemplatePath, $ProjectDir, $ProjectName, $false)

        ## Copy *.Targets files and update .csproj file
        $Message = $LocalizedData.DeployTargetFilesToProject -f $ProjectDir
        Write-Verbose -Message $Message

        Copy-Item -Path $ModuleBasePath\SDK\Microsoft.Coresys.Common.Targets -Destination $ProjectDir -Force
        Copy-Item -Path $ModuleBasePath\SDK\Microsoft.CoreSys.CSharp.Targets -Destination $ProjectDir -Force

        $ProjectObj = $DTE.Solution.Projects | ? ProjectName -eq $ProjectName
        $ProjectFile  = $ProjectObj.FullName

        $Class1FileItem = $ProjectObj.ProjectItems | ? Name -eq "Class1.cs"
        if ($Class1FileItem -eq $null)
        {
            ## In some VS instances, 'Solution.AddFromTemplate' doesn't create the placeholder
            ## file 'Class1.cs'. We manually create the file in this case.
            $CSharpClassTemplateFile = $DTE.solution.GetProjectItemTemplate("Class", "CSharp")
            $ProjectObj.ProjectItems.AddFromTemplate($CSharpClassTemplateFile, "Class1.cs")
            $Class1FileItem = $ProjectObj.ProjectItems | ? Name -eq "Class1.cs"
        }
        $Class1FilePath = $Class1FileItem.Properties.Item("FullPath").Value
        
        $SolutionExplorer = $DTE.Windows.Item([EnvDTE.Constants]::vsWindowKindSolutionExplorer)

        ## Save the project if not yet
        ## Because we are about to unload it
        if (-not $ProjectObj.Saved) {
            $ProjectObj.Save()
        }

        $Message = $LocalizedData.UpdateProjectFile -f $ProjectFile
        Write-Verbose -Message $Message

        ## Unload the project to update the project file
        $SolutionExplorer.Activate()
        $ProjectObj.DTE.ExecuteCommand("Project.UnloadProject")

        ## Update the project file
        Update-ProjectFile -ProjectFile $ProjectFile -OutputType $OutputType
        Update-SampleCode -SampleFile $Class1FilePath -OutputType $OutputType

        ## Reload the project after the update
        $SolutionExplorer.Activate()
        $ProjectObj.DTE.ExecuteCommand("Project.ReloadProject")

        ## Select 'Class1.cs' and open it in code editor
        $Class1SolutionPath = "$($DTE.Solution.Properties.Item("Name").Value)\$ProjectName\Class1.cs"
        $SolutionExplorer.Object.GetItem($Class1SolutionPath).Select([EnvDTE.vsUISelectionType]::vsUISelectionTypeSelect)

        $DTE.ItemOperations.OpenFile($Class1FilePath, [EnvDTE.Constants]::vsViewKindCode) > $null

    } catch {

        $Message = $LocalizedData.UnknownDTEFailure -f $_.Exception.Message, $LocalizedData.FollowManualSteps
        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId "UnknownDTEFailure" `
                   -CallerPSCmdlet $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }

    $Message = $LocalizedData.DoneCreatingCSharpProject -f $ProjectName
    Write-Verbose -Message $Message
}

##
## Write out the instructions for manually seting up NanoPS SDK and creating C# project targeting it
##
function Show-SdkSetupReadMe
{
    <#
      .SYNOPSIS
      Opens the SDK root folder in File Explorer and opens the README.txt file for manual setup.
       
      .DESCRIPTION
      This cmdlet opens the SDK root folder in File Explorer. It also opens the README.txt file which contains detailed instructions for setting up the SDK manually and creating Visual Studio C# projects targeting CoreCLR and PowerShell Core included in the Windows Server 2016 Nano Server.
       
      .EXAMPLE
      PS C:\> Show-SdkSetupReadMe
       
      This command opens the SDK root folder in File Explorer. It also opens the README.txt file which contains detailed instructions for setting up the SDK manually.
    #>


    [CmdletBinding()]
    param()

    $SdkFolder  = Join-Path -Path $PSCmdlet.MyInvocation.MyCommand.Module.ModuleBase -ChildPath SDK
    $ReadmePath = Join-Path -Path $SdkFolder -ChildPath README.txt
    
    Invoke-Item -Path $SdkFolder
    & "$env:windir\system32\notepad.exe" $ReadmePath
}

##
## Install CoreCLR remote debugger to a NanoServer instance
##
function Install-RemoteDebugger
{
    <#
      .SYNOPSIS
      Installs and configures the Visual Studio remote debugger on a Nano Server machine.
       
      .DESCRIPTION
      This cmdlet copies the Visual Studio remote debugger binaries to a Nano Server machine and configures the debugger to accept connections. After installing the remote debugger, you can use Start-RemoteDebugger and Stop-RemoteDebugger to start and stop the debugger.
 
      NOTE: Please make sure the following Visual Studio prerequisites are installed prior to using the cmdlets:
         - Visual Studio 2015 Update 2 [or higher]
         - Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK
         - If you don't have Visual Studio 2015 installed, you can install Visual Studio Community 2015 from
           https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx
 
      .EXAMPLE
      PS C:\> $session = New-PSSession -ComputerName RemoteNanoServer
      PS C:\> Install-RemoteDebugger -Session $session
 
      The first command creates a PSSession to a remote machine running Nano Server. The second command installs the Visual Studio remote debugger to the Nano Server machine using the PSSession.
 
      .PARAMETER Session
      Specifies a PowerShell remoting session (PSSession) opened to the target Nano Server. You can create a new PSSession using the New-PSSession cmdlet.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.Runspaces.PSSession]
        $Session
    )

    $VSDebuggerBinariesPath = "${env:ProgramFiles(x86)}\Common Files\Microsoft Shared\Phone Tools\14.0\Debugger\target\x64"
    $VSDebuggerLibPath = "${env:ProgramFiles(x86)}\Common Files\Microsoft Shared\Phone Tools\14.0\Debugger\target\lib"
    $VSDebuggerOneCorePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio 14.0\VC\redist\onecore"

    if (-not (Test-Path HKLM:\SOFTWARE\Microsoft\DevDiv\VC\Servicing\14.0))
    {
        $Message = $LocalizedData.RequireVisualStudio2015 -f $LocalizedData.InstallVisualStudio2015
        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId "RequireVisualStudio2015" `
                   -CallerPSCmdlet $PSCmdlet `
                   -ExceptionObject $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }
    
    if (-not (Test-Path -Path $VSDebuggerBinariesPath) -or
        -not (Test-Path -Path $VSDebuggerOneCorePath))
    {
        $Message = $LocalizedData.PrerequisitesForRemoteDebugger
        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId "PrerequisitesMissing" `
                   -CallerPSCmdlet $PSCmdlet `
                   -ExceptionObject $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }

    if (Get-Command -Name Copy-Item -ParameterName ToSession -ErrorAction Ignore)
    {
        $CanCopyRemotely = $true
        if ($Session.State -ne "Opened")
        {
            $Message = $LocalizedData.SessionNotOpened
            ThrowError -ExceptionName "System.ArgumentException" `
                       -ExceptionMessage $Message `
                       -ErrorId "SessionNotOpened" `
                       -CallerPSCmdlet $PSCmdlet `
                       -ExceptionObject $Session `
                       -ErrorCategory InvalidArgument
        }
    }
    else
    {
        $CanCopyRemotely = $false
        $Message = $LocalizedData.CannotCopyRemotely -f $PSVersionTable.PSVersion.ToString()
        Write-Warning -Message $Message
    }

    $OldErrorPreference = $ErrorActionPreference
    try {
        $ErrorActionPreference = "Stop"
        $VerbosePrefix = [string]::Empty
        if ($Host.Name -eq "Package Manager Host")
        {
            $VerbosePrefix = "VERBOSE: "
        }

        if ($CanCopyRemotely)
        {
            ## Create the target folders
            $RemoteSystemDrive = Invoke-Command -Session $Session -ScriptBlock { 
                    $null = New-Item -Path $env:SystemDrive\NanoServerRemoteDebugger -ItemType Directory
                    $null = New-Item -Path $env:SystemDrive\NanoServerRemoteDebugger\CoreCLR -ItemType Directory
                    $env:SystemDrive
                }

            $RemoteDebuggerFolder = "$RemoteSystemDrive\NanoServerRemoteDebugger"
            $RemoteSystem32Folder = "$RemoteSystemDrive\Windows\System32"
            $RemoteLocalCoreCLRFolder = "$RemoteSystemDrive\NanoServerRemoteDebugger\CoreCLR"
            $RemoteDotNetCoreFolder = "$RemoteSystemDrive\Windows\System32\DotNetCore\v1.0"

            $Params = @{ ToSession = $Session }
        }
        else
        {
            ## Create local temporary folders
            $TempTargetDir = Join-Path -Path $env:TEMP -ChildPath RemoteDebuggerPack
            if (Test-Path -Path $TempTargetDir)
            {
                Remove-Item -Path $TempTargetDir -Recurse -Force
            }

            $RemoteDebuggerFolder = "$TempTargetDir\NanoServerRemoteDebugger"
            $RemoteSystem32Folder = "$TempTargetDir\System32"
            $RemoteLocalCoreCLRFolder = "$TempTargetDir\NanoServerRemoteDebugger\CoreCLR"
            $RemoteDotNetCoreFolder = "$TempTargetDir\DotNetCore"
            $InstallScriptPath = "$TempTargetDir\install.ps1"

            $null = New-Item -Path $RemoteDebuggerFolder -ItemType Directory -Force
            $null = New-Item -Path $RemoteSystem32Folder -ItemType Directory -Force
            $null = New-Item -Path $RemoteLocalCoreCLRFolder -ItemType Directory -Force
            $null = New-Item -Path $RemoteDotNetCoreFolder -ItemType Directory -Force

            $Params = @{}
        }

        ## Copy the 64-bit OneCore remote debugger binaries to Nano Server
        $TargetPath = if ($CanCopyRemotely) { $LocalizedData.CopyToNanoServer -f $RemoteDebuggerFolder } else { $RemoteDebuggerFolder }
        $Message = $LocalizedData.CopyOneCoreDebuggerBits -f $VerbosePrefix, $TargetPath
        Write-Verbose -Message $Message
        Copy-Item @Params -Path "$VSDebuggerBinariesPath\*" -Destination $RemoteDebuggerFolder -Recurse -Force
                
        ## Copy platform-agnostic binaries
        $Message = $LocalizedData.CopyPlatformAgnosticBits -f $VerbosePrefix, $TargetPath
        Write-Verbose -Message $Message
        Copy-Item @Params -Path "$VSDebuggerLibPath\*" -Destination $RemoteDebuggerFolder -Recurse -Force

        ## Copy the CRT (Debug & Release)
        $TargetPath = if ($CanCopyRemotely) { $LocalizedData.CopyToNanoServer -f $RemoteSystem32Folder } else { $RemoteSystem32Folder }
        $Message = $LocalizedData.CopyCRTBits -f $VerbosePrefix, $TargetPath
        Write-Verbose -Message $Message
        Copy-Item @Params -Path "$VSDebuggerOneCorePath\debug_nonredist\x64\Microsoft.VC140.DebugCRT\vcruntime140d.dll" -Destination $RemoteSystem32Folder -Force
        Copy-Item @Params -Path "$VSDebuggerOneCorePath\debug_nonredist\x64\Microsoft.VC140.DebugCRT\msvcp140d.dll" -Destination $RemoteSystem32Folder -Force
        Copy-Item @Params -Path "${env:ProgramFiles(x86)}\Windows Kits\10\bin\x64\ucrt\ucrtbased.dll" -Destination $RemoteSystem32Folder -Force
        Copy-Item @Params -Path "$VSDebuggerOneCorePath\x64\Microsoft.VC140.CRT\vcruntime140.dll" -Destination $RemoteSystem32Folder -Force
        Copy-Item @Params -Path "$VSDebuggerOneCorePath\x64\Microsoft.VC140.CRT\msvcp140.dll" -Destination $RemoteSystem32Folder -Force

        ## Copy local CoreCLR binaries, which are solely required by the CoreCLR remote debugger
        $TargetPath = if ($CanCopyRemotely) { $LocalizedData.CopyToNanoServer -f $RemoteLocalCoreCLRFolder } else { $RemoteLocalCoreCLRFolder }
        $Message = $LocalizedData.CopyLocalCoreCLRBits -f $VerbosePrefix, $TargetPath
        Write-Verbose -Message $Message
        Copy-Item @Params -Path "${env:CommonProgramFiles(x86)}\Microsoft Shared\Phone Tools\12.0\Debugger\target\x64\*" -Destination $RemoteLocalCoreCLRFolder -Force

        ## Copy RoMetadata.dll and CoreCLR debugging dlls
        $ModuleBasePath = $PSCmdlet.MyInvocation.MyCommand.Module.ModuleBase
        $RometadataPath = Join-Path -Path $ModuleBasePath -ChildPath "Debugger\RoMetadata.dll"
        $CoreCLRDbgPath = Join-Path -Path $ModuleBasePath -ChildPath "Debugger\CoreCLR"

        $TargetPath = if ($CanCopyRemotely) { $LocalizedData.CopyToNanoServer -f $RemoteDotNetCoreFolder } else { $RemoteDotNetCoreFolder }
        $Message = $LocalizedData.CopyCoreCLRDebugBits -f $VerbosePrefix, $TargetPath
        Write-Verbose -Message $Message
        Copy-Item @Params -Path $RometadataPath -Destination $RemoteDebuggerFolder -Force
        Copy-Item @Params -Path "$CoreCLRDbgPath\*" -Destination $RemoteDotNetCoreFolder -Force

        if ($CanCopyRemotely)
        {
            ## Register the debugger on Nano Server
            $Message = $LocalizedData.ConfigureRemoteDebugger -f $VerbosePrefix
            Write-Verbose -Message $Message
            Invoke-Command -Session $Session -ScriptBlock {
                    reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\Appx /v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f > $null
                    netsh advfirewall firewall add rule name="Remote Debugger" dir=in action=allow program="$env:SystemDrive\NanoServerRemoteDebugger\msvsmon.exe" enable=yes > $null
                }
        }
        else
        {
            ## Generate install.ps1 that does the actuall install on the NanoServer
            $InstallScript = @'
    $NanoServerRemoteDebuggerDir = Join-Path -Path $PSScriptRoot -ChildPath NanoServerRemoteDebugger
    $System32Dir = Join-Path -Path $PSScriptRoot -ChildPath System32
    $DotNetCoreDir = Join-Path -Path $PSScriptRoot -ChildPath DotNetCore
 
    Copy-Item -Path $NanoServerRemoteDebuggerDir -Destination "$env:SystemDrive\" -Recurse -Force
    Copy-Item -Path "$System32Dir\*" -Destination "$env:SystemDrive\Windows\System32" -Recurse -Force
    Copy-Item -Path "$DotNetCoreDir\*" -Destination "$env:SystemDrive\Windows\System32\DotNetCore\v1.0" -Recurse -Force
 
    reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\Appx /v AllowDevelopmentWithoutDevLicense /t REG_DWORD /d 1 /f > $null
    netsh advfirewall firewall add rule name="Remote Debugger" dir=in action=allow program="$env:SystemDrive\NanoServerRemoteDebugger\msvsmon.exe" enable=yes > $null
'@

            
            [System.IO.File]::WriteAllText($InstallScriptPath, $InstallScript, [System.Text.Encoding]::ASCII)
            Invoke-Item -Path $TempTargetDir

            $Message = $LocalizedData.ManualInstallSteps -f $TempTargetDir
            Write-Warning -Message $Message
        }
        
    } finally {
        $ErrorActionPreference = $OldErrorPreference
    }
}

##
## Start the remote debugger to accept connection
##
function Start-RemoteDebugger
{
    <#
      .SYNOPSIS
      Starts the remote debugger on a remote machine running Nano Server.
       
      .DESCRIPTION
      This cmdlet starts the remote debugger on a remote machine running Nano Server.
       
      NOTE: Please make sure the following Visual Studio prerequisites are installed prior to using the cmdlets:
         - Visual Studio 2015 Update 2 [or higher]
         - Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK
         - If you don't have Visual Studio 2015 installed, you can install Visual Studio Community 2015 from
           https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx
 
      After the remote debugger is started, you can attach to the debugger from Visual Studio as follows:
        - Click the "Debug" menu, then select "Attach to Process..." to open the Attach to Process window
        - From the Transport drop-down menu, select "Remote (no authentication)"
        - In the Qualifier field, type the IP address of the remote Nano Server machine
        - Click the Refresh button to refresh the list of available processes
        - From the list of available processes, select the process you want to attach to, and then click "Attach"
        - For example, attach to "wsmprovhost.exe" to debug a module that is running in a remote PowerShell session
 
      .EXAMPLE
      PS C:\> $session = New-PSSession -ComputerName RemoteNanoServer
      PS C:\> Start-RemoteDebugger -Session $session
 
      The first command creates a PSSession to a remote machine running Nano Server. The second command starts the Visual Studio remote debugger on the target Nano Server machine using the PSSession.
 
      .PARAMETER Session
      Specifies a PowerShell remoting session (PSSession) opened to the target Nano Server. You can create a new PSSession using the New-PSSession cmdlet.
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [System.Management.Automation.Runspaces.PSSession]
        $Session
    )

    Invoke-Command -Session $Session -ScriptBlock { Start-Process -FilePath $env:SystemDrive\NanoServerRemoteDebugger\msvsmon.exe -ArgumentList "/nowowwarn /noauth /anyuser /nosecuritywarn /timeout:36000" }
}

##
## Stop the remote debugger
##
function Stop-RemoteDebugger
{
    <#
      .SYNOPSIS
      Stops the remote debugger on a remote machine running Nano Server.
 
      .DESCRIPTION
      This cmdlet stops the remote debugger on a remote machine running Nano Server.
       
      NOTE: Please make sure the following Visual Studio prerequisites are installed prior to using the cmdlets:
         - Visual Studio 2015 Update 2 [or higher]
         - Windows and Web Development -> Universal Windows App Development Tools -> Tools (1.3.1 [or higher]) and Windows 10 SDK
         - If you don't have Visual Studio 2015 installed, you can install Visual Studio Community 2015 from
           https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx
 
      .EXAMPLE
      PS C:\> $session = New-PSSession -ComputerName RemoteNanoServer
      PS C:\> Stop-RemoteDebugger -Session $session
 
      The first command creates a PSSession to a remote machine running Nano Server. The second command stops the Visual Studio remote debugger on the target Nano Server machine using the PSSession.
 
      .PARAMETER Session
      Specifies a PowerShell remoting session (PSSession) opened to the target Nano Server. You can create a new PSSession using the New-PSSession cmdlet.
    #>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [System.Management.Automation.Runspaces.PSSession]
        $Session
    )

    Invoke-Command -Session $Session -ScriptBlock { Get-Process -Name msvsmon -ErrorAction SilentlyContinue | Stop-Process -force -ErrorAction Stop }
}