Microsoft.PowerShell.NanoServer.SDK.psm1


$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(Mandatory = true, 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();
        }}
    }}
}}
"@


## Load localized error strings
Import-LocalizedData LocalizedData -filename NanoPowerShellSDK.Resource.psd1

#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 = [System.Security.Principal.WindowsPrincipal]::new($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)
    
    $RefNode = $Xml.Project.PropertyGroup[0].Item("TargetPlatformVersion")
    $NewNode = $Xml.CreateNode($RefNode.NodeType, "TargetFrameworkVersion", $RefNode.NamespaceURI)
    $NewNode.InnerText = "v0.1"
    $Xml.Project.PropertyGroup[0].InsertBefore($newNode, $refNode) > $null
    $Xml.Project.PropertyGroup[0].OutputType = $OutputType
    $Xml.Project.Import[-1].Project = "Microsoft.CoreSys.CSharp.targets"

    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

        ## Replace LF with CR+LF to make it consistent in Visual Studio
        Set-Content -Path $SampleFile -Value $SampleCode -Encoding UTF8 -Force -NoNewline
    }
}

##
## Save XmlDocument to the specified file
##
function Save-Xml
{
    param(
        [xml] $XmlDoc,
        [string] $Path
    )

    $XmlWriterSetting = [System.Xml.XmlWriterSettings]::new()
    $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
      This cmdlet creates a new Visual Studio C# Project targeting CoreCLR and PowerShell deployed in TP5 NanoServer. Initial setup of the SDK will be performed if it's not done already.
       
      This cmdlet should 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.
      - Make sure you select the following features before starting installation:
          "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs"
          "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK"
 
      .DESCRIPTION
      This cmdlet helps you to create Visual Studio C# projects targeting the CoreCLR and PowerShell reference assemblies for TP5 NanoServer.
      - 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 DLL library or EXE program.
 
      This cmdlet first checks if the initial setup is done. If not, it will try copying the reference assemblies to the reference assembly folder of Visual Studio 2015.
 
      MODULE_PREREQUISITES
        - New-NanoCSharpProject => Visual Studio 2015 Update 2 with the feature "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs" installed.
        - Install-RemoteDebugger => Visual Studio 2015 Update 2 with the feature "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK" installed.
 
      Please make sure Visual Studio 2015 Update 2 and the ncessary features are installed before using the cmdlets.
 
      .EXAMPLE
      PM> New-NanoCSharpProject -Path D:\Projects -ProjectName DISMCmdlet -SolutionName DISM -OutputType Library -Verbose
 
      Create a new C# project in a new sulution targeting CoreCLR and PowerShell deployed in TP5 NanoServer. The new project produces a DLL.
 
      .EXAMPLE
      PM> New-NanoCSharpProject -Path D:\Temp -ProjectName Project2 -AddToCurrentSolution -OutputType EXE -Verbose
 
      Create a new C# project in the currently open solution targeting CoreCLR and PowerShell deployed in TP5 NanoServer. The new project produces an EXE.
 
      .PARAMETER Path
      Path of the project
 
      .PARAMETER ProjectName
      Name of the new project
 
      .PARAMETER SolutionName
      Name of the new solution
 
      .PARAMETER OutputType
      Output file type of the new project
 
      .PARAMETER AddToCurrentSolution
      Indicate to create the new 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

    if (-not (Test-Path -Path $RefAsmInfoPath -PathType Leaf))
    {
        $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 TP5 reference assemblies to 'v0.1' folder
                if ($WriteProgress) {{
                    $Status = 'Copy TP5 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
                [pscustomobject]@{{ NanoPS = 'TP5' }} | 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\Windows 8\Windows\Class Library (Windows 8.1)", "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["Name"].Value)
                Write-Verbose -Message $Message

                $DTE.Solution.Close($true)
            }

            ## Create the new solution
            $DTE.Solution.Create($SolutionDir, $SolutionName)
            $SolutionFilePath = $dte.Solution.Properties["Path"].Value
            $DTE.Solution.SaveAs($SolutionFilePath)
        }
        else
        {
            $Message = $LocalizedData.CreateProjectInCurrentSolution -f ($DTE.Solution.Properties["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"
        $Class1FilePath = $Class1FileItem.Properties["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["Name"].Value)\$ProjectName\Class1.cs"
        $SolutionExplorer.Object.GetItem($Class1SolutionPath).Select([EnvDTE.vsUISelectionType]::vsUISelectionTypeSelect)

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

    } catch {

        if ($_.FullyQualifiedErrorId -eq "System.IO.FileNotFoundException")
        {
            $Message = $LocalizedData.RequireExtraVSFeature
            $ErrorId = "RequireExtraVSFeature"
        }
        else
        {
            $Message = $LocalizedData.UnknownDTEFailure -f $_.Exception.Message, $LocalizedData.FollowManualSteps
            $ErrorId = "UnknownDTEFailure"
        }

        ThrowError -ExceptionName "System.InvalidOperationException" `
                   -ExceptionMessage $Message `
                   -ErrorId $ErrorId `
                   -CallerPSCmdlet $PSCmdlet `
                   -ErrorCategory InvalidOperation
    }

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

    ##
    ## Write out warning message about the 'Developer mode' prompt in Settings
    ##
    if ($PSCmdlet.ParameterSetName -eq "NewSolution")
    {
        $SettingProcess = Get-WmiObject Win32_Process -Filter "Name='SystemSettings.exe'"
        if ($SettingProcess -ne $null -and $SettingProcess.ExecutablePath -eq "$env:windir\ImmersiveControlPanel\SystemSettings.exe")
        {
            Write-Warning -Message $LocalizedData.DeveloperModeWarning
        }
    }
}

##
## Write out the instructions for manually seting up NanoPS SDK and creating C# project targeting it
##
function Get-SDKSetupSteps
{
    <#
      .SYNOPSIS
      This cmdlet opens up the README.txt for manual setup, which contains the detailed instructions for creating C# projects targeting the CoreCLR and PowerShell reference assemblies for TP5 NanoServer.
    #>


    [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
      This cmdlet installs the remote debugger to a NanoServer via an opened PSSession targeting the NanoServer. The remote debugger allows you to remotely debug CoreCLR applications running on the NanoServer.
       
      This cmdlet depends on Visual Studio 2015 Update 2.
      - 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.
      - Make sure you select the following features before starting installation:
          "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs"
          "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK"
 
      .DESCRIPTION
      This cmdlet copies the necessary debugger binaries to the NanoServer and configure the remote debugger to accept connections.
      After successfully installing the remote debugger, you can use Start-RemoteDebugger and Stop-RemoteDebugger for your debugging.
 
      MODULE_PREREQUISITES
        - New-NanoCSharpProject => Visual Studio 2015 Update 2 with the feature "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs" installed.
        - Install-RemoteDebugger => Visual Studio 2015 Update 2 with the feature "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK" installed.
 
      Please make sure Visual Studio 2015 Update 2 and the ncessary features are installed before using the cmdlets.
 
      .EXAMPLE
      PM> Install-RemoteDebugger -Session $session -Verbose
 
      Install the remote debugger to the NanoServer that $session is targeting.
 
      .PARAMETER Session
      An opened powershell remote session targeting the NanoServer
    #>


    [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
'@

            
            Set-Content -Path $InstallScriptPath -Value $InstallScript -Encoding Ascii -NoNewline -Force
            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
      This cmdlet starts the remote debugger on a NanoServer.
       
      This cmdlet depends on Visual Studio 2015 Update 2.
      - 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.
      - Make sure you select the following features before starting installation:
          "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs"
          "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK"
 
      .DESCRIPTION
      This cmdlet starts the remote debugger on a NanoServer. Once the remote debugger is started, do the following steps in Visual Studio to attach to the debugger:
        - Click "Debug" menu, then select "Attach to Process..."
        - Select "Remote (no authentication)" for "Transport"
        - Type the IP address of the NanoServer for "Qualifier", and then click "Refresh" button
        - From the list of available processes, select the process you want to attach, and then click "Attach"
          For example, attach to "wsmprovhost.exe" if you want to debug your module that is running in powershell.
 
      MODULE_PREREQUISITES
        - New-NanoCSharpProject => Visual Studio 2015 Update 2 with the feature "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs" installed.
        - Install-RemoteDebugger => Visual Studio 2015 Update 2 with the feature "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK" installed.
 
      Please make sure Visual Studio 2015 Update 2 and the ncessary features are installed before using the cmdlets.
 
      .EXAMPLE
      PM> Start-RemoteDebugger -Session $session
 
      Start the remote debugger on the NanoServer that $session is targeting.
 
      .PARAMETER Session
      An opened powershell remote session targeting the NanoServer
    #>


    [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
      This cmdlet stops the remote debugger on a NanoServer.
       
      This cmdlet depends on Visual Studio 2015 Update 2.
      - 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.
      - Make sure you select the following features before starting installation:
          "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs"
          "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK"
 
      .DESCRIPTION
      This cmdlet stops the remote debugger on a NanoServer.
 
      MODULE_PREREQUISITES
        - New-NanoCSharpProject => Visual Studio 2015 Update 2 with the feature "Windows and Web Development -> Windows 8.1 and Windows Phone 8.0/8.1 Tools -> Tools and Windows SDKs" installed.
        - Install-RemoteDebugger => Visual Studio 2015 Update 2 with the feature "Universal Windows App Development Tools -> Tools (1.3) and Windows 10 SDK" installed.
 
      Please make sure Visual Studio 2015 Update 2 and the ncessary features are installed before using the cmdlets.
 
      .EXAMPLE
      PM> Stop-RemoteDebugger -Session $session
 
      Stop the remote debugger on the NanoServer that $session is targeting.
 
      .PARAMETER Session
      An opened powershell remote session targeting the NanoServer
    #>


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

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