Windows Server 2016 as a Platform for Modern Apps

Overview

Over the few years, the adoption rate of containers for application development has increased significantly while improving the overall development process for traditional cross-platform based applications. To accommodate this requirement and growth, Microsoft has adopted the same technology for Windows Server 2016, with assistance from Docker, to provide the same ease of use deployment and development of Windows Applications on Windows Containers. Additionally, Windows Server 2016 also comes with Nano Server, a trimmed down and remotely manageable windows installation that is suited for both cloud based solutions and a DevOps workflow.

With these new capabilities, Hosting Providers are able to offer new services running in slimmer environments with fewer attack vectors than are possible on the Desktop Experience version of the Windows Server Operating System.

For the Implementation described in this document, the following Windows Server 2016 capabilities are leveraged:

  • Nano Server
  • Windows Containers
  • PowerShell Remoting

The following sections explain these capabilities and also enumerate considerations that the Hosting Service Provider (HSP) Operations Administrator has to keep in mind while implementing this offer.

Operational Considerations

For this Offering it is important to have a moderate understanding of the following technologies:

  • Hyper-V
  • Nano Server
  • Windows Containers
  • PowerShell

An Azure Subscription is only required for the Manage Nano Server using Server Management Tools in Azure (Optional). This section is completely optional and isn't required to complete the Scenarios in this Offering.

Windows Server 2016 Capabilities for Modern Apps

Below is a brief description of the capabilities and benefits of using the Nano Server and Windows Container technologies that are available in Windows Server 2016.

Nano Server

Windows Server 2016 offers a new installation option: Nano Server. Nano Server is a remotely administered server operating system optimized for private clouds and datacenters. It is similar to Windows Server in Server Core mode, but significantly smaller, has no local logon capability, and only supports 64-bit applications, tools, and agents. It takes up far less disk space, sets up significantly faster, and requires far fewer updates and restarts than Windows Server. When it does restart, it restarts much faster. The Nano Server installation option is available for Standard and Datacenter editions of Windows Server 2016.

The important differences between Nano Server and Server Core or Server with Desktop Experience are listed below:

  • Nano Server is "headless;" there is no local logon capability or graphical user interface.
  • Only 64-bit applications, tools, and agents are supported.
  • Nano Server cannot serve as an Active Directory domain controller.
  • Group Policy is not supported. However, you can use Desired State Configuration to apply settings at scale.
  • Nano Server cannot be configured to use a proxy server to access the internet.
  • NIC Teaming (specifically, load balancing and failover, or LBFO) is not supported. Switch-embedded teaming (SET) is supported instead.
  • System Center Configuration Manager and System Center Data Protection Manager are not supported.
  • The version of Windows PowerShell provided with Nano Server has important differences. For details, see PowerShell on Nano Server.
  • Nano Server is supported only on the Current Branch for Business (CBB) model--there is no Long-Term Servicing Branch (LTSB) release for Nano Server at this time. See the following subsection for more information.

Windows Containers

Containers are isolated, resource controlled, and portable operating environments where an application can run without affecting the rest of the system and without the system affecting the application. Containers are the next evolution in virtualization.

If you were inside a container, it would look very much like you were inside a freshly installed physical computer or a virtual machine. And, to Docker, a Windows Server Container can be managed in the same way as any other container. Windows Containers include two different types, or runtimes.

Windows Server Containers – provide application isolation through process and namespace isolation technology. A Windows Server container shares a kernel with the container host and all containers running on the host.

Hyper-V Containers – expand on the isolation provided by Windows Server Containers by running each container in a highly optimized virtual machine. In this configuration, the kernel of the container host is not shared with the Hyper-V Containers.

Secure Management

Both Nano Server and Windows containers can run within Windows Server 2016 Shielded VMs.

Nano Server is natively accessible via PowerShell Remoting over standard PowerShell remote sessions or PowerShell direct. By default, only VMs located within the same subnet are able to access the Nano Server using PowerShell Remoting. Nano Server can be configured to use WinRM over HTTPS using Certificates for additional security.

Secure Deployment

Both Nano Server and Windows containers have a reduced attack surface as they are much smaller and more agile than standard virtual machines and can be configured specifically for the applications that run within them without having additional applications, services or network ports exposed needlessly.

The rest of this document will focus on two scenarios using many of the capabilities in Nano Server and Windows Containers.

Scenario 1 – Deploying a .NET Core Application to Nano Server

Contoso Inc. is looking to consolidate and migrate their existing applications from running on overprovisioned VMs to Nano Server. To start, they want to deploy a new ASP.NET Core Application to Nano Server.

Contoso's requirements are detailed below

  • Application needs to run on Nano Server.
  • Minimal Administrative Access needs to be granted to the Application.
  • Nano Server must be manageable from On-Premise or the Azure Cloud.
  • A way to automate or script the Deployment Process must be possible.

Contoso currently doesn't have a requirement for Guarded Fabric or Guarded VMs to host the Nano Servers On-Premise, but may choose to implement these solutions at a later date.

Deployment configuration

A .NET Core Application (1.0.1) will be deployed to a Nano Server VM hosted on a Windows Server 2016 Hyper-V Host. The Application being deployed will be accessible via HTTP on Port 8000. For simplicity purposes, all Files and Folders utilized in this Scenario will take place on the C:\ Drive of both the Hyper-V Server and the Nano Server.

Deployment Options

For this scenario, you have the option to either deploy the Nano Server using the Image Builder GUI or using a PowerShell Script. The Table below details the path for each going through each option along with links to the respective steps in the document.

 

Deployment Option 1

Deployment Option 2

Step 1

Deploy Nano Server using Image Builder GUI

Deploy Nano Server using PowerShell Scripting

Step 2

Add Nano Server to WSMan TrustedHosts (Optional)

Installing the .NET Core Module (Optional)

Step 3

Installing the .NET Core Module (Optional)

Copy Prerequisite Files to the Nano Server

Step 4

Copy Prerequisite Files to the Nano Server

Install .NET Core 1.0.1 on the Nano Server

Step 5

Install .NET Core 1.0.1 on the Nano Server

Update IIS on the Nano Server

Step 6

Update IIS on the Nano Server

Deploy .NET Core Application to the Nano Server

Step 7

Deploy .NET Core Application to the Nano Server.

 

Prerequisites

The following should already be in place before continuing with the Scenario.

  • Windows Server 2016 Standard or higher ISO.
  • Domain Controller running Windows Server 2012 R2 or higher.
  • Windows Server 2016 Standard or higher running Hyper-V.

This scenario can be completed without a Domain Controller, but was included as a prerequisite as Active Directory is used in most large-scale Windows Environments.

Make note of the following Hyper-V Manager Settings on your Hyper-V Server. The values for each of the settings below will be need to be replaced to reflect the setting values on your Hyper-V Server.

Setting

Value

Virtual Hard Disks

C:\Hyper-V\Virtual Hard Disks

Virtual Machine Configurations

C:\Hyper-V\Configuration Files

Virtual Switch

External Switch – 192.168.73.30

All PowerShell commands and scripts will be executed from the Windows Server 2016 Server running Hyper-V.

Finally, make sure to download the ModernApps.zip file from the Modern Apps Offering in the Windows-Server-2016-In-A-Box GitHub Repository and extract it to the root of C:\ on the Hyper-V Server.

Deploy Nano Server using Image Builder GUI

Install Windows ADK

In order to use the Nano Server Image Builder GUI, the Windows Assessment and Deployment Kit (ADK) needs to be downloaded and installed. The instructions below are for Windows ADK for Windows 10, version 1607 which can be downloaded from here.

Once the binary has been downloaded, double-click on it. Leave the default installation path in place and click Next.

Next, choose no on the Windows Kit Privacy and click Next.

Read the License Agreement and click Accept.

Leave the default feature selections and click Install.

The Windows ADK should finish installing in about 30 minutes depending your available bandwidth. When the installation is finished, click on Close.

Install Nano Server Image Builder

The Nano Server Image Builder is a Graphical Interface that allows you to create Nano Server Images and bootable USB Drives. The installer can be downloaded from here. The instructions below use Nano Server Image Builder 1.0.78.

Launch the installer, NanoServerImageBuilder.msi and click Next.

Agree to the End-User License Agreement and click Next.

Leave the default value of the Destination Folder and click Next.

Next, click on Install.

When the installation is complete, click Finish.

Create a new Nano Server Image (VHD)

Next, double-click on NanoServerImageBuilder.exe located in C:\Program Files\Nano Server Image Builder to launch the application. Click on Create a new Nano Server image.

On the Before you begin page, review the information presented to you and click Next.

On the Create a Nano Server image page, click on Browse and select the Drive Letter that contains the Windows Server 2016 ISO and click Next.

On the License page, agree to the licensing terms and click on Next.

On the Select deployment type page, select Virtual machine image and then in the text field values as shown in the table below.

Field Name

Value

Nano Server image output file name

C:\ModernApps\Software\NanoServer\GeneratedImages\nanosrv-vm-5.vhd

Set max size extension for the VHD or VHDX

8 GB

Specify a directory to copy the image creation logs files

C:\ModernApps\Software\NanoServer\Generatedimages

Once the values have been set, click Next.

On the Basic installation page, click Next.

On the select optional packages page, set the Nano Server edition to Standard and select Web Server (IIS). Afterwards, click Next.

On the Add drivers page, click Next.

On the Destination machine information page, set the Computer name to nanosrv-vm-5, set the Administrator password to P@ssw0rd1! and finally, Set the Time zone according to your location. Afterwards, click Next.

On the Join domain page, select Join domain and provide the name of the domain to join in the Domain name field. Afterwards, click Next.

On the Set network page, scroll down to Configure network settings and manually set the IP Address configuration of your Nano Server if you want. Enable the WinRM and remote PowerShell connections from all subnet and the virtual LAN settings if you need the capabilities as described on the page. After you are done, click Next.

On the Select next step page, click on Create basic Nano Server image.

On the Review all settings page, review the information and click Create.

The creation of the Nano Server image should finish in about 5 minutes. Afterwards click Close.

Deploy the Nano Server image (VHD) to Hyper-V

After you have finished creating your Nano Server image, move the VHD file from C:\ModernApps\Software\NanoServer\GeneratedImages to C:\Hyper-V\Virtual Hard Disks

Next, open up the Hyper-V Manager and click on New à Virtual Machine… On the Before you begin page, click Next.

On the Specify Name and Location Page, type in the Name of the virtual machine and click Next.

On the Specify Generation page, select Generation 1 and click Next.

On the Assign Memory page, change Startup memory to 2048 and click Next.

On the Configure Networking page, choose the virtual switch to connect to and click Next.

On the Connect Virtual Hard Disk page, choose Use an existing virtual hard disk, click Browse and choose the Nano Server image (VHD) that resides at C:\Hyper-V\Virtual Hard Disks. Afterwards, click Next.

On the Completing the New Virtual Machine Wizard page, click Finish.

In the Hyper-V Manager console, right-click on the newly created VM and click on Start.

Configure networking for Nano Server VM

There is currently an issue with the existing release of the Nano Server Image Builder where adding the Static IP Address configuration to the VM during the image creation isn't always applied and reverts back to DHCP. Instructions to setup networking for the Nano Server VM are below.

Open up a virtual machine connection session to the Nano Server in the Hyper-V Console and login with the local administrator password you set earlier during the image creation

Once logged in, select Networking and hit Enter.

Select Ethernet and hit Enter.

Hit F11 to go into the IPv4 Settings.

Hit F4 to toggle from DHCP Enabled to Disabled.

Hit the Tab key to cycle through and type in the values you want to set for IP Address, Subnet Mask and the Default Gateway. When the fields have been filled, hit Enter twice to Save.

Once the operation comes back with Operating succeeded, hit Esc.

The Network Adapter Settings page should now display the Static IP Address you just set.

Keep hitting Esc until you are logged out and back at the Console login.

Next, open up an elevated PowerShell prompt on the Hyper-V Server and add the newly assigned static IP Address to your TrustedHosts configuration.

Set-Item WSMan:\localhost\Client\TrustedHosts -Value 192.168.73.195 -Concatenate -Force

Next, and create a new PowerShell remoting session to the Nano Server. Note that the first time creating a session to a newly created Nano Server VM may take a minute or so.

$Session = New-PSSession -ComputerName 192.168.73.195 -Credential ~\administrator

Next, run the following command to add DNS Servers to the Nano Server networking configuration. Replace 192.168.73.191 and 8.8.8.8 with the DNS Servers you want to use.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        # Retrieving Current IPv4 Ethernet Address

        $Interface = Get-NetIPAddress | Where-Object {($_.AddressFamily -match "IPv4") -and ($_.InterfaceAlias -match "Ethernet")}

            

        # Setting the DNS Servers on the IPv4 Ethernet Address

        Set-DNSClientServerAddress `

            -InterfaceIndex $Interface.InterfaceIndex `

            -ServerAddress "192.168.73.191,8.8.8.8"

    }

Next, run the command below to verify that the DNS Settings were applied successfully.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        Get-DNSClientServerAddress | FT -AutoSize

    }

Verify PowerShell Remoting using a Domain Admin account.

If you would like to verify connectivity to the domain that you joined the Nano Server VM to, follow the instructions below.

Open up a new elevated PowerShell prompt on the Hyper-V Server and add the FQDN of the Nano Server VM to your TrustedHosts configuration.

Set-Item WSMan:\localhost\Client\TrustedHosts -Value nanosrv-vm-5.lumagate.com -Concatenate -Force

Next, and create a new PowerShell remoting session to the Nano Server.

$Session = New-PSSession -ComputerName nanosrv-vm-5.lumagate.com -Credential lumagate\serveradmin

Next, run the following command to verify connectivity through the PowerShell remoting session.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        $ComputerInfo = Get-ComputerInfo

        $ComputerInfo.CSName

        $ComputerInfo.CSDomain

        Get-NetIPAddress | FT -AutoSize

    }

You should get back the basic information about the Nano Server's computer name, domain name and networking information.

Deploy Nano Server using PowerShell Scripting

The Nano Server for this Scenario will be deployed using the PowerShell Script deploy-new-nano-server-on-prem.ps1.

Open up an elevated PowerShell Prompt and run the following commands to copy over the required Nano Server Image Generator files to C:\ModernApps\software\NanoServer\Base from the Windows Server 2016 ISO. Make sure to change D:\ in the commands below reflect the location of where the ISO is mounted.

Copy-Item D:\NanoServer\NanoServer.wim -Destination C:\ModernApps\software\NanoServer\Base

Copy-Item D:\NanoServer\Packages\ -Recurse -Destination C:\ModernApps\software\NanoServer\Base

Next, cd to C:\ModernApps\Scripts and use the criteria below to deploy a new Nano Server using the script.

The Syntax for the Script is as follows:./deploy-new-nano-server-on-prem.ps1 `

-NanoImageGeneratorPSModule "<PATH_TO_NANO_IMAGE_PS_MODULE>" `

-BasePath "<PATH_TO_NANO_IMAGE_GENERATOR_BOOT_MEDIA>" `

-TargetPath "<PATH_TO_NANO_SERVER_GENERATED_IMAGES>" `

-VHDPath "<PATH_TO_HYPER-V_VHD_FILES>" `

-NanoServerName "<NANO_SERVER_NAME>" `

-NanoServerPassword "<NANO_SERVER_PASSWORD>" `

-SwitchName "<HYPER-V_SWITCH_NAME>" `

-Ipv4Address "<NANO_SERVER_IP_ADDRESS>" `

-Ipv4SubnetMask "<NANO_SERVER_SUBNET_MASK>" `

-Ipv4Gateway "<NANO_SERVER_GATEWAY>" `

-Ipv4DnsPrimary "<NANO_SERVER_PRIMARY_DNS_SERVER>" `

-Ipv4DnsSecondary "<NANO_SERVER_SECONDARY_DNS_SERVER>" `

-DomainName "<DOMAIN_NAME>"

An Example is shown below:

./deploy-new-nano-server-on-prem.ps1 `

-NanoImageGeneratorPSModule "C:\ModernApps\Software\NanoServer\NanoServerImageGenerator" `

-BasePath "C:\ModernApps\Software\NanoServer\Base" `

-TargetPath "C:\ModernApps\Software\NanoServer\GeneratedImages" `

-VHDPath "C:\Hyper-V\Virtual Hard Disks" `

-NanoServerName "nanosrv-vm-5" `

-NanoServerPassword "P@ssw0rd1!" `

-SwitchName "External Switch - 192.168.73.0" `

-Ipv4Address "192.168.73.195" `

-Ipv4SubnetMask "255.255.255.0" `

-Ipv4Gateway "192.168.73.30" `

-Ipv4DnsPrimary "192.168.73.191" `

-Ipv4DnsSecondary "8.8.8.8" `

-DomainName "lumagate.com"

Output after successful execution is shown below.

Additional information about deploy-new-nano-server-on-prem.ps1. can be reviewed in the script itself using your favorite text editor.

Add Nano Server to WSMan Trusted Hosts (Optional)

If you deployed the Nano Server using the Nano Server Image Builder GUI, you will be required to add the Nano Server to the TrustedHosts configuration of the Hyper-V Server.

If you deployed the Nano Server using the PowerShell script deploy-new-nano-server-on-prem.ps1, this section is optional but recommended.

If you wanted to manage the Nano Server from a different host you must do the following in order to be able to manage the Nano Server using PowerShell remoting.

First, make sure that the Windows Remote Management Service is running.

Start-Service winrm

Next, add the Nano Server Hostname to the TrustedHosts configuration.

Set-Item WSMan:\localhost\Client\TrustedHosts -Value nanosrv-vm-5 -Concatenate -Force

Verify that you can connect to the Nano Server using either local credentials:

Enter-PSSession -ComputerName nanosrv-vm-5 -credential ~\administrator

or domain credentials:

Enter-PSSession -ComputerName nanosrv-vm-5 -credential lumagate\serveradmin

Installing the .NET Core Module (Optional)

All prerequisite files required for the deployment of the .NET Core Application to the Nano Server are included in the ModernApps.zip file; however, the process for manually generating the ASP.NET Core Module files required on the Nano Server has been included for thoroughness.

The ASP.NET Core Module is an IIS 7.5+ module responsible for process management of ASP.NET Core HTTP listeners and to proxy requests to processes that it manages. At this time, it is currently a manual process to Install the ASP.NET Core Module for IIS and then copy the required files from the installation to Nano Server.

Please note that these instructions can be ran on a separate Windows Server 2016 Server or on the Windows Server 2016 running Hyper-V Server that was deployed earlier as part of the prerequisites.

Start by opening up an elevated PowerShell prompt and installing IIS on the Host.

Install-WindowsFeature Web-Server

Next, download the .NET Core for Windows Server Hosting from here.

Put a checkmark next to the license terms and conditions and click on Install.

Once the Installation is completed, click on Close.

The two files that are required to be copied over to the Nano Server are now available locally on the Host.

C:\Windows\System32\inetsrv\aspnetcore.dll

C:\Windows\System32\inetsrv\config\schema\aspnetcore_schema.xml

Next, copy the files to: C:\ModernApps\Software\ dotNETCoreWindowsServerHostingFiles

Copy Prerequisite Files to the Nano Server

Next, open an elevated PowerShell prompt on the Hyper-V Server, cd to C:\ModernApps\Scripts and create a new PowerShell remoting session to the Nano Server.

$Session = New-PSSession -ComputerName nanosrv-vm-5 -Credential lumagate\serveradmin

You will be prompted to enter in the password for the Credentials you entered.

Run the Get-PSSession command to verify that the PowerShell remoting session is open.

Run the following commands to copy over the ASP.NET Core Module files.

Copy-Item -Path C:\ModernApps\Software\dotNETCoreWindowsServerHostingFiles\aspnetcore.dll -Destination C:\Windows\System32\inetsrv\aspnetcore.dll -ToSession $Session

Copy-Item -Path C:\ModernApps\Software\dotNETCoreWindowsServerHostingFiles\aspnetcore_schema.xml -Destination C:\windows\system32\inetsrv\config\schema\aspnetcore_schema.xml -ToSession $Session

Install .NET Core 1.0.1 on the Nano Server

The latest version of .NET Core can be found here. For this offering, .NET Core 1.0.1 will be installed.

On the Hyper-V Server, open an elevated PowerShell prompt, cd to C:\ModernApps\Scripts and run the install-dotnet-core-1.0.1-on-nano-server.ps1. PowerShell script. Use the example command below for your environment.

./install-dotnet-core-1.0.1-on-nano-server.ps1 `

-NanoServerName nanosrv-vm-5 `

-Username serveradmin `

-DomainName lumagate.com

You should receive the following output if the command was successful.

Additional information about install-dotnet-core-1.0.1-on-nano-server.ps1. can be reviewed in the script itself using your favorite text editor.

Update IIS on the Nano Server

The IIS Configuration on the Nano Server has to be updated in order to work with ASP.NET Core Application.

On the Hyper-V Server, open an elevated PowerShell prompt, cd to C:\ModernApps\Scripts and run the update-nano-server-iis-for-dotnet-core-apps.ps1. PowerShell script. Use the example command below for your environment.

./update-nano-server-iis-for-dotnet-core-apps.ps1 `

-NanoServerName nanosrv-vm-5 `

-Username serveradmin `

-DomainName lumagate.com

You should receive the following output if the command was successful.

Additional information about update-nano-server-iis-for-dotnet-core-apps.ps1. can be reviewed in the script itself using your favorite text editor.

Deploy the .NET Core Application to the Nano Server

On the Hyper-V Server, open an elevated PowerShell, cd to C:\ModernApps\Scripts and create a new PowerShell remoting session to the Nano Server. If you still have your PowerShell prompt open from the previous section, you can use the existing $Session variable.

$Session = New-PSSession -ComputerName nanosrv-vm-5 -Credential lumagate\serveradmin

You will be prompted to enter in the password for the Credentials you entered.

Run the Get-PSSession command to verify that the PowerShell remoting session is open.

Run the following command to create a directory where the .NET Core Application will reside on the Nano Server.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        New-Item C:\CoreWebApp -Type Directory

    }

You should receive the following output if the command was successful.

Next, copy over the ASP.NET Core application to the Nano Server. This command may run for a few minutes.

Copy-Item -Path C:\ModernApps\Software\CoreWebApp\* -Recurse -Destination C:\CoreWebApp -ToSession $Session

Next, update the path to the dotnet.exe file in the web.config file to point to the location of where it currently resides on the Nano Server, C:\dotnet\dotnet.exe

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        (Get-Content C:\CoreWebApp\web.config) `

            -replace 'dotnet', 'C:\dotnet\dotnet.exe' `

            | Set-Content C:\CoreWebApp\web.config

    }

You can verify that the path to dotnet.exe was updated successfully by running the following command and looking for processPath="C:\dotnet\dotnet.exe".

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        cat C:\CoreWebApp\web.config

    }

Next, create a new Firewall Rule to allow access to the .NET Core Application on Port 8000.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        New-NetFirewallRule `

            -Name "CoreWebApp8000" `

            -DisplayName "CoreWebApp - HTTP (8000)" `

            -Protocol tcp -LocalPort 8000 `

            -Action Allow `

            -Enabled True | FT -AutoSize

    }

Next, create a new IIS site for the .NET Core Application.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {    

        Import-Module IISAdministration

        New-IISSite `

            -Name "CoreWebApp" `

            -PhysicalPath C:\CoreWebApp `

            -BindingInformation "*:8000:"

    }

Next, verify that the site is running and accessible.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {    

        Import-Module IISAdministration

        Get-IISSite -Name CoreWebApp | FT -AutoSize

    }

Finally, open up a web browser and browse to the Nano Server's hostname or FQDN on Port 8000.

Manage Nano Server using Server Management Tools in Azure (Optional)

Server management tools is an Azure service that allows you to remotely manage machines running Windows Server 2016, including Nano Server. This is achieved by setting up a Gateway Server in your on-premise environment to connect to the server s you want to manage.

In this instructions below, the Hyper-V Host that has been used earlier in this Scenario will be setup as the Gateway Server.

Start off by logging into your Azure Subscription, click on the New Resource button and search for Server management tools. Once the blade opens up, click on the Create button.

Under Computer Name, type in the name of the Nano Server you want to manage. Type in the name of the Resource group you want to create for this resource and finally, type in the name you want to give to the Gateway Server you are setting up on premise. Once you are done, click on the Create button.

The Deployment will start and should complete in a couple minutes.

Next, open up the Resource Group you created and click on the Gateway you created. You will see an orange prompt requesting you to finish setting up the Gateway, click on it.

In the Gateway Configuration Blade, leave the defaults in place and click on the Generate a package link button. Use the copy link button to the right of the generated URL to download the Gateway MSI Package to the server you are setting up as a Gateway Server.

Once you've finished downloading the zip file, extract it and double-click on GatewayServer.msi. Note that the name of the zip file will be the same as the Gateway Server name you set earlier when deploying the Azure Service.

Put a checkmark in the I accept the terms in the License Agreement checkbox and click Install.

Choose the Generate a self-signed certificate option and click Next.

Once the installation has completed, click Finish.

Next, go back to your session that is still open on the Azure Portal and refresh the Gateway Blade. You should see both the name of your Gateway Server and Nano Server appear.

Next, click on the name of your Nano Server from the previous screenshot, a new blade for your Nano Server will open up. Click on the Manage As button.

Type in the credentials you normally use to administer the Nano Server and click on the OK button. Note that you have the option to save your credentials in Azure.

Metrics will start appearing for your server in the blade as well as several management options will be become available. Explore all the Tools available to you as you see fit.

Troubleshooting

The information below is helpful for troubleshooting the .NET Core Application deployment to Nano Server.

Enabling access to Nano Server Event Logs

In order to access the Event Logs on the Nano Server (located in C:\Windows\system32\winevt\Logs), you need to enable the following Firewall Rules on the Nano Server.

  • Windows Management Instrumentation (DCOM-In)
  • Windows Management Instrumentation (WMI-In)
  • Windows Management Instrumentation (WMI-Out)

On the Hyper-V Server, open an elevated PowerShell, cd to C:\ModernApps\Scripts and create a new PowerShell remoting session to the Nano Server. If you still have your PowerShell prompt open from the previous section, you can use the existing $Session variable.

$Session = New-PSSession -ComputerName nanosrv-vm-5 -Credential lumagate\serveradmin

Next, run the following command to enable the three firewall rules previously mentioned above.

Invoke-Command `

    -Session $Session `

    -ScriptBlock `

    {

        Get-NetFirewallRule `

            -Name WMI-RPCSS-In-TCP `

            | Enable-NetFirewallRule `

            -PassThru | FL DisplayName, Enabled

        Get-NetFirewallRule `

            -Name WMI-WINMGMT-In-TCP `

            | Enable-NetFirewallRule `

            -PassThru | FL DisplayName, Enabled

        Get-NetFirewallRule `

        -Name WMI-WINMGMT-Out-TCP `

        | Enable-NetFirewallRule `

        -PassThru | FL DisplayName, Enabled

    }

You should get the following output back showing that all three firewall rules are now Enabled.

Retrieving Windows Event Log entries

Once you have enabled the three firewall rules from the previous section, you will not be able to retrieve Windows Event Log entries using the get-nano-server-event-logs.ps1.

For example, if I wanted to retrieve entries from the last 10 days in the Application Log, I would use the following syntax.

./get-nano-server-event-logs.ps1 `

-NanoServerName nanosrv-vm-5 `

-Username serveradmin `

-DomainName lumagate.com `

-LogFile Application `

-Days 10

The results of this search will look similar to what is shown below.

Verifying your version of .NET Core

Run the following command to verify that the Version of .NET Core you are running is compatible.

C:\dotnet\dotnet.exe C:\CoreWebApp\CoreWebApp.dll

It is common to find that the version of .NET Core that you are running is wrong and that it will be generating error messages in your Application Log with no information referring back to the actual problem. Example output from Application logs is shown below:

-----------------------------------------------------------------------------Category : 0CategoryString : NoneEventCode : 1000EventIdentifier : 1000TypeEvent :InsertionStrings : {Failed to start process with commandline '"C:\dotnet\dotnet.exe" .\CoreWebApp.dll', ErrorCode = '0x80004005'.}

LogFile : ApplicationMessage :RecordNumber : 55SourceName : IIS AspNetCore ModuleTimeGenerated : 20161102114802.207464-000TimeWritten : 20161102114802.207464-000Type : ErrorUserName :-----------------------------------------------------------------------------

Example output from a PowerShell Prompt of the .NET Core Version missing is shown below:

-----------------------------------------------------------------------------[testvm]: PS C:\dotnet> C:\dotnet\dotnet.exe C:\CoreWebApp\CoreWebApp.dll

C:\dotnet\dotnet.exe: The specified framework 'Microsoft.NETCore.App', version '1.0.1' was not found.

+ CategoryInfo : NotSpecified: (The specified f... was not found.:String) [], RemoteException

+ FullyQualifiedErrorId : NativeCommandError

- Check application dependencies and target a framework version installed at: C:\dotnet\shared\Microsoft.NETCore.App - The following versions are installed: 1.0.0 - Alternatively, install the framework version '1.0.1'.-----------------------------------------------------------------------------

Scenario 2 – Deploying an ASP.NET 4.5 based application to a Windows Container running Windows Server Core

Contoso Inc. is looking to consolidate and migrate their existing applications from running on overprovisioned VMs to Windows Containers. To start, they want to deploy an ASP.NET 4.5 based application to a Windows container running Windows Server Core.

Contoso's requirements are detailed below

  • Application needs to run on Windows Server Core.
  • Minimal Administrative Access needs to be granted to the Application.
  • A way to automate or script the Deployment Process must be possible.

Deployment configuration

An ASP.NET 4.5 based application will be deployed to a Windows container running Windows Server Core hosted on a Windows Server 2016 Hyper-V Host. The Application being deployed will be accessible via HTTP on Port 8000. For simplicity purposes, all Files and Folders utilized in this Scenario will take place on the host.

Prerequisites

The following should already be in place before continuing with the Scenario.

  • Windows Server 2016 Standard or Datacenter.

All PowerShell commands and scripts will be executed from the Windows Server 2016 Server.

Finally, make sure to download the ModernApps.zip file from the Modern Apps Offering in the Windows-Server-2016-In-A-Box GitHub Repository and extract it to the root of C:\ on the Windows Server 2016 Server.

Deployment Options

The Table below details the path for deploying this Scenario with links to the respective steps in the document.

 

Deployment Tasks

Step 1

Install Docker

Step 2

Install the microsoft/aspnet Container

Step 3

Build and deploy a Container for the ASP.NET 4.5.2 Application

Install Docker

Before installing Docker, make sure all cumulative updates have been installed on the Windows Server 2016 Server.

Login to your Windows Server 2016 host and open an elevated PowerShell prompt and run the following command to install the OneGet PowerShell Module.

Install-Module -Name DockerMsftProvider -Repository PSGallery -Force

Next, install the latest version of Docker using OneGet.

Install-Package -Name docker -ProviderName DockerMsftProvider

A restart is now required before you continue, run the following command to restart the Windows Server 2016 Host.

shutdown -t 1 -r

Install the microsoft/aspnet Container

The microsoft/aspnet container is a Windows Server Core based container with ASP.NET 4.5.2 and IIS already installed; as such, it is the ideal container for this Scenario. Note that you can customize this container to deploy ASP.NET 3.5 and IIS as well.

After the server is back online, login and open an elevated PowerShell prompt and run the following command to download the Windows Server Core base container images. Due to the size of windows containers, this could take several minutes.

docker pull microsoft/windowsservercore

Afterwards, run the following command to download the microsoft/aspnet container.

docker pull microsoft/aspnet

Build and deploy a Container for the ASP.NET 4.5.2 Application

This section will cover the process of creating a custom container using a Dockerfile, starting the container, and the finally verifying that the ASP.NET 4.5.2 Application is available on Port 8000.

Overview of the aspnet452 Dockerfile

A Dockerfile is a text file containing all of the commands a user could call on the command line to assemble an image using the docker build command. In other words, if you want to automate the creation of custom containers in your environment, use Dockerfiles.

Next, we will go over the aspnet452.dockerfile that was extracted from the ModernApps.zip and is now located in C:\ModernApps\software\Docker\.

The code in the Dockerfile is shown below:

#

# Sample Docker File for deploying an ASP.NET 4.5.2 Application to a

# microsoft/aspnet container.

#

FROM microsoft/aspnet

MAINTAINER Ryan Irujo ryan.irujo@lumagate.com

RUN mkdir C:\dotNet452WebApp

RUN powershell -NoProfile -Command \

Import-Module IISAdministration ; \

New-IISSite -Name "ASPNET452" -PhysicalPath C:\dotNet452WebApp -BindingInformation "*:8000:"

EXPOSE 8000

ADD dotNet452WebApp/ /dotNet452WebApp

Once the Dockerfile is called from the docker build command, it will do the following:

  • Create a new custom container from the microsoft/aspnet container.
  • Create a new directory called C:\dotNet452WebApp in the container.
  • Import the IISAdministration Module and then create a new IIS Website called ASPNET452 located in C:\dotNet452WebApp and listening on port 8000.
  • Port 8000 will be exposed on the container at runtime.
  • The Application Files located in C:\software\Docker\dotNet452WebApp are copied over to C:\dotNet452WebApp on the container.

The Table below provides a brief explanation of each Instruction utilized in the Dockerfile. The official Dockerfile reference documentation can be found here.

Instruction

Description

FROM

Sets the Base Image for subsequent instructions and is the required first instruction in a Dockerfile.

MAINTAINER

Allows you to set the Author field of the generated images.

RUN

Executes any commands in a new layer on top of the current image and then commits the results. The resulting committed image will be used for the next step in the Dockerfile.

EXPOSE

Instructs Docker that the container listens on a specific network port at runtime. This does not make the port on the container accessible to the host, this must be done using the -p flag when starting a container to forward traffic from the exposed port to a specific port on the container host.

ADD

Copies files or directories or remote file URLs and adds them to the filesystem of the container at the path given.

Build and run the custom Container for the ASP.NET 4.5.2 Application

Login to your Windows Server 2016 Host and open an elevated PowerShell prompt, cd to C:\ModernApps\software\Docker and run the following command

docker build -t aspnet452webapp -f C:\ModernApps\software\Docker\aspnet452.dockerfile .

You should get similar output shown in the screenshot below once the build is finished.

Next, run the following command to start the container.

docker run -d -p 8000:8000 --name webapp aspnet452webapp

Run the following command to verify the container has started

docker ps -a

Next, run the following command to find the IP address of the container.

docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" webapp

Next, browse to the IP Address of the container on port 8000 and you should see the application.

http://172.18.239.147:8000/

Additional Notes about Docker

On Windows Server 2016, all of the images and containers for Docker will be located in the directory C:\ProgramData\docker. The docker.exe and dockerd.exe binaries are installed in C:\Program Files.

Useful Docker Commands

Some common and useful docker commands are in the table below:

Description

Command

List all containers (running or stopped)

docker ps -a

Stop all running containers

docker stop $(docker ps -a -q)

Remove all stopped containers

docker rm $(docker ps -a -q)

Delete all existing images

docker rmi $(docker ps -a -q)

Show history of an image

docker history "<IMAGE_ID>"

Return IP Address of a container

docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" "<CONTAINER_NAME>"

Attach to a running container with a PowerShell Prompt

docker exec -i -t "<CONTAINER_ID>" powershell

Attach to a running container with a Command Prompt

docker exec -i -t "<CONTAINER_ID>" cmd

References

Nano Server

Nano Server Package Cmdlets

Nano Server

ASP.NET Core on Nano Server

Nano Server

Viewing Application, Security and System Event Logs using WMI

Nano Server

How to access Nano Server

Windows Containers

Windows Containers on Windows Server

Windows Containers

microsoft/aspnet

Server Management Tools

Managing Nano Server with Server Management Tools

Server Management Tools

Deploy & Setup Server management tools

Nano Server

WinRM cannot process the request, Kerberos authentication error 0x80090322

Nano Server

NanoServerPackage cannot be installed because the catalog signature does not match the hash


Platform for Modern apps Scripts

add-user-to-nano-server.ps1

<#

.SYNOPSIS
This script creats a new user Account to an existing Nano Server and adds the user to the Local Administrators Group.

.DESCRIPTION
This script creats a new user Account to an existing Nano Server and adds the user to the Local Administrators Group.

Once this script is launched, the following actions will occur:

- The IP Address of the FQDN of the targeted Nano Server will be added to the TrustedHosts of the Computer this script is ran from.
- A new PowerShell Session will be created targeting the Nano Server.
  - Prompt will appear to login to the targeted Nano Server using the 'Username' Parameter.
- The new user Account will be created on the Nano Server.
- The new user Account will be added to the Local Administrators Group on the Nano Server.

This script has checks in place verifying if the targeted Nano Server is already added to the TrustedHosts entry on the Computer this 
script is ran from. Additionally, this script will check to see if the new user Account is already added to the Nano Server and if it
is already a member of the Local Administrators Group. This Script is written to work with both Nano Server hosted On-Premise or in 
the Azure Cloud.

To Login to an existing Nano Server after the new user account has been added, the following syntax must be used:
 - Syntax:  Enter-PSSession -ComputerName  -Credential \
 - Example: Enter-PSSession -ComputerName nanosrv-vm-4 -Credential nanosrv-vm-4\nanoadmin

.PARAMETER NanoServerName
The IP Address or the FQDN of the Nano Server.

.PARAMETER Username
This is either the Administrative Account of the Nano Server or a Domain Account with rights to login to the Nano Server.

.PARAMETER NewUsername
The Username of the new User Account being added to the Nano Server.

.PARAMETER NewPassword
The Password of the new User Account being added to the Nano Server.

.PARAMETER FullName
The Full Name of the new User Account being added to the Nano Server.

.PARAMETER Description
The Description of the new User Account being added to the Nano Server.

.NOTES
Filename:   add-user-to-nano-server.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

.Example
Add a New User to an existing Nano Server VM joined to a Domain:

./add-user-to-nano-server.ps1 `
-NanoServerName nanosrv-vm-4 `
-Username serveradmin `
-DomainName lumagate.com `
-NewUsername nanoadmin `
-NewPassword P@ssw0rd1! `
-FullName "Nano Server Administrator" `
-Description "Nano Server Administrator"

Add a New User to an existing Nano Server VM, stand-alone in Azure:

./add-user-to-nano-server.ps1 `
-NanoServerName luma-nanosrv-r3.westeurope.cloudapp.azure.com `
-Username winadmin `
-NewUsername nanoadmin `
-NewPassword P@ssw0rd1! `
-FullName "Nano Server Administrator" `
-Description "Nano Server Administrator"

#>

param
(
    [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$Username,

    [String]$DomainName,

    [Parameter(Mandatory)]
    [String]$NewUsername,

    [Parameter(Mandatory)]
    [String]$NewPassword,

    [Parameter(Mandatory)]
    [String]$FullName,

    [Parameter(Mandatory)]
    [String]$Description
)

# Starting the WinRM Service if it isn't already Running.
If ((Get-Service | Where-Object {$_.Name -match "WinRM"}).Status -ne "Running")
{
    Start-Service -Name "WinRM"

    If ($?)
    {
        Write-Output "WinRM Service Started on Localhost."
    }

    If (!$?)
    {
        Write-Output "Failed to Start WinRM Service on Localhost."
        exit 2
    }
}

# Checking to see if the Public IP FQDN or Address of the Nano Server already exists in TrustedHosts.
If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -match $NanoServerName)
{
    Write-Output "$NanoServerName already exists in TrustedHosts."
}

If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -notmatch $NanoServerName)
{
    Write-Output "$NanoServerName was not found in TrustedHosts."

    # Adding The Public IP FQDN or Address of the Nano Server to TrustedHosts.
    Set-Item WSMan:\localhost\Client\TrustedHosts $NanoServerName -Concatenate -Force

    If ($?)
    {
        Write-Output "$NanoServerName was Successfully added to TrustedHosts in the WS-Management Configuration."
    }

    If (!$?)
    {
        Write-Output "Failed to add $NanoServerName to TrustedHosts in the WS-Management Configuration."
    }
}

If ($DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Domain Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential $DomainName\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError        

    If ($?)
    {
        Write-Output "New PowerShell Session created Successfully using Domain Credentials."
    }

    If (!$?)
    {
        Write-Output "Failed to create a new PowerShell Session using Domain Credentials."
        Write-Output $PSSessionError.Exception
        exit 2        
    } 
}

If (!$DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Local Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential $Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError

    If ($?)
    {
        Write-Output "New PowerShell Session created Successfully using Local Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Local Credentials."
        Write-Output $PSSessionError.Exception
        exit 2
    } 
}

# Connecting to the PowerShell Session and updating the Nano Server.
$Results = Invoke-Command `
    -Session $Session `
    -ScriptBlock {param($NanoServerName,$NewUsername,$NewPassword,$FullName,$Description)

        # Creating the Local User on the Nano Server.
        New-LocalUser $NewUsername `
            -FullName $FullName `
            -AccountNeverExpires `
            -Description $Description `
            -Password (ConvertTo-SecureString -String $NewPassword -AsPlainText -Force) `
            -ErrorVariable AddLocalUserError `
            -ErrorAction SilentlyContinue

        If (!$AddLocalUserError)
        {
            $AddLocalUserResult = Write-Output "$NewUsername was added Succesfully to the Nano Server, $NanoServerName."
        }

        If ($AddLocalUserError)
        {
            $AddLocalUserErrorMessage = $AddLocalUserError.Exception
            $AddLocalUserResult = Write-Output "$NewUsername was not added to the Nano Server, $NanoServerName."
        }

        # Adding the new User to the Local Administrators Group on the Nano Server.
        Add-LocalGroupMember `
            -Group administrators `
            -Member $NewUsername `
            -ErrorVariable AddLocalUserToGroupError `
            -ErrorAction SilentlyContinue

        If (!$AddLocalUserToGroupError)
        {
            $AddLocalUserToGroupResult = Write-Output "$NewUsername was added to Local Administrators Group Successfully on the Nano Server, $NanoServerName."
        }

        If ($AddLocalUserToGroupError)
        {
            $AddLocalUserToGroupErrorMessage = $AddLocalUserToGroupError.Exception
            $AddLocalUserToGroupResult = Write-Output "$NewUsername was not added to the Local Administrators Group on the Nano Server, $NanoServerName."
        }

        # Adding Results of Configuration to a new PSCustomObject that will be outputted in the $Results Variable.
        New-Object PSObject `
        -Property @{
            AddLocalUserResult              = $AddLocalUserResult
            AddLocalUserErrorMessage        = $AddLocalUserErrorMessage
            AddLocalUserToGroupResult       = $AddLocalUserToGroupResult
            AddLocalUserToGroupErrorMessage = $AddLocalUserToGroupErrorMessage            
        }
    } -ArgumentList $NanoServerName,$NewUsername,$NewPassword,$FullName,$Description

# Returned Results.
$Results.AddLocalUserResult
$Results.AddLocalUserErrorMessage
$Results.AddLocalUserToGroupResult
$Results.AddLocalUserToGroupErrorMessage

deploy-new-nano-server-on-prem.ps1

<#

.SYNOPSIS
This script deploys a new Nano Server VM to an existing Hyper-V Host and joins the Nano Server to an existing Domain.

.DESCRIPTION
This script deploys a new Nano Server VM to an existing Hyper-V Host and joins the Nano Server to an existing Domain.
This script is designed to run from the Hyper-V Host where the Nano Server is being deployed; however, this script could
be modifed to run from a different location if required.

Once this script is launched, the following actions will occur:

- The WinRM Service will be started locally if it isn't already running.
- The PowerShell Module for Generating Nano Server Images is imported.
- The NanoServerPassword is converted to a Secure String.
- A new Nano Server Image (VHD) is created.
- The Nano Server Image (VHD) is moved to the location where the Hyper-V Hard Disks are stored.
- A new Nano Server VM is created on the Hyper-V Host.
- A new VHD Data Disk for the Nano Server VM is created on the Hyper-V Host.
- The new VHD Disk is added to the Nano Server VM.
- The Nano Server VM is started.

After the Nano Server is deployed, it is recommended to change the default password of the local 'administrator' or disable it entirely.
Additionally, adding a unique local administrator account or associating one from the Domain the Nano Server is joined to, is recommended.

.PARAMETER NanoImageGeneratorPSModule
The location of the Nano Image Generate PowerShell Module.

.PARAMETER MediaPath
The location of the Windows Server 2016 ISO that contains the Nano Server Image Generator Files.

.PARAMETER BasePath
The location of the Boot Media,Temporary Files, and Nano Server Image Generation Logs.

.PARAMETER TargetPath
The location of where the Generated Nano Server Images (VHD) will be generated and saved.

.PARAMETER VHDPath
The location of where the Generated Nano Server Image (VHD) will be moved to and attached to the Hyper-V Host.

.PARAMETER NanoServerName
The Name of the Nano Server.

.PARAMETER NanoServerPassword
The Password of the local 'administrator' account of the Nano Server.

.PARAMETER SwitchName
The Name of the Hyper-V Switch the Nano Server will be added to.

.PARAMETER Ipv4Address
The Static IP Address of the Nano Server VM.

.PARAMETER Ipv4SubnetMask
The Subnet Mask of the Nano Server VM.

.PARAMETER Ipv4Gateway
The Gateway Address of the Nano Server VM.

.PARAMETER Ipv4DnsPrimary
The Primary DNS Server of the Nano Server VM.

.PARAMETER Ipv4DnsSecondary
The Secondary DNS Server of the Nano Server VM.

.PARAMETER DomainName
The Domain that the Nano Server VM is being joined to.

.NOTES
Filename:   deploy-new-nano-server-on-prem.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

.EXAMPLE          
./deploy-new-nano-server-on-prem.ps1 `
-NanoImageGeneratorPSModule "C:\ModernApps\Software\NanoServer\NanoServerImageGenerator" `
-BasePath "C:\ModernApps\Software\NanoServer\Base" `
-TargetPath "C:\ModernApps\Software\NanoServer\GeneratedImages" `
-VHDPath "C:\Hyper-V\Virtual Hard Disks" `
-NanoServerName "nanosrv-vm-5" `
-NanoServerPassword "P@ssw0rd1!" `
-SwitchName "External Switch - 192.168.73.0" `
-Ipv4Address "192.168.73.195" `
-Ipv4SubnetMask "255.255.255.0" `
-Ipv4Gateway "192.168.73.30" `
-Ipv4DnsPrimary "192.168.73.191" `
-Ipv4DnsSecondary "8.8.8.8" `
-DomainName "lumagate.com"

#>

param
(
    [Parameter(Mandatory)]
    [String]$NanoImageGeneratorPSModule,

    [Parameter(Mandatory)]
    [String]$BasePath,

    [Parameter(Mandatory)]
    [String]$TargetPath,

    [Parameter(Mandatory)]
    [String]$VHDPath,

    [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$NanoServerPassword,

    [Parameter(Mandatory)]
    [String]$SwitchName,

    [Parameter(Mandatory)]
    [String]$Ipv4Address,
 
    [Parameter(Mandatory)]
    [String]$Ipv4SubnetMask,

    [Parameter(Mandatory)]
    [String]$Ipv4Gateway,

    [Parameter(Mandatory)]
    [String]$Ipv4DnsPrimary,

    [Parameter(Mandatory)]
    [String]$Ipv4DnsSecondary,

    [Parameter(Mandatory)]
    [String]$DomainName
)

# Starting the WinRM Service if it isn't already Running.
If ((Get-Service | Where-Object {$_.Name -match "WinRM"}).Status -ne "Running")
{
    Start-Service -Name "WinRM"

    If ($?)
    {
        Write-Output "WinRM Service Started locally."
    }

    If (!$?)
    {
        Write-Output "Failed to Start the WinRM Service locally."
        exit 2
    }
}

# Importing the PowerShell Module for Generating Nano Server Images.
Import-Module $NanoImageGeneratorPSModule -Verbose

# Converting the NanoServerPassword to a Secure String.
$SecuredNanoServerPassword = ConvertTo-SecureString -String $NanoServerPassword -AsPlainText -Force

# Creating a new Nano Server Image (VHD).
New-NanoServerImage `
    -Edition Standard `
    -DeploymentType Guest `
    -Package Microsoft-NanoServer-IIS-Package `
    -BasePath $BasePath `
    -TargetPath "$TargetPath\$NanoServerName\$NanoServerName.vhd" `
    -ComputerName $NanoServerName `
    -AdministratorPassword $SecuredNanoServerPassword `
    -InterfaceNameOrIndex Ethernet `
    -Ipv4Address $Ipv4Address `
    -Ipv4SubnetMask $Ipv4SubnetMask `
    -Ipv4Gateway $Ipv4Gateway `
    -Ipv4Dns $Ipv4DnsPrimary,$Ipv4DNSSecondary `
    -DomainName $DomainName `
    -ReuseDomainNode

If ($?)
{
    Write-Output "Nano Server Image (VHD) $NanoServerName created Successfully."
}

If (!$?)
{
    Write-Output "Failed to create Nano Server Image (VHD) $NanoServerName."
    exit 2
}

# Moving the new Nano Server Image (VHD) to the location where the Hyper-V Hard Disks are stored.
Move-Item `
    -Path "$TargetPath\$NanoServerName\$NanoServerName.vhd" `
    -Destination  "$VHDPath\$NanoServerName.vhd"

If ($?)
{
    Write-Output "Moved the Nano Server Image (VHD) $NanoServerName to $VHDPath\$NanoServerName.vhd."
}

If (!$?)
{
    Write-Output "Failed to Move the Nano Server Image (VHD) $NanoServerName to $VHDPath\$NanoServerName.vhd."
    exit 2
}

# Creating a new Nano Server VM on the Hyper-V Host.
New-VM `
    -Name $NanoServerName `
    -ComputerName localhost `
    -Generation 1 `
    -MemoryStartupBytes 2GB `
    -VHDPath "$VHDPath\$NanoServerName.vhd" `
    -BootDevice VHD `
    -SwitchName $SwitchName `
    -ErrorAction SilentlyContinue `
    -ErrorVariable NewVMError

If ($?)
{
    Write-Output "New VM $NanoServerName created Successfully."
}

If ($NewVMError)
{
    Write-Output "Failed to create New VM $NanoServerName."
    Write-Output $NewVMError.Exception
    exit 2
}

# Creating a new VHD Data Disk for the Nano Server VM.
New-VHD `
    -ComputerName localhost `
    -Path "$VHDPath\$NanoServerName-data-0.vhd" `
    -Dynamic `
    -SizeBytes 50GB

If ($?)
{
    Write-Output "VHD Data Disk, $NanoServerName-data-0.vhd, created Successfully for $NanoServerName."
}

If (!$?)
{
    Write-Output "Failed to create VHD Data Disk, $NanoServerName-data-0.vhd, for $NanoServerName."
    exit 2
}

# Adding the new VHD Disk to the Nano Server VM.
Add-VMHardDiskDrive `
    -VMName $NanoServerName `
    -ComputerName localhost `
    -Path "$VHDPath\$NanoServerName-data-0.vhd"

If ($?)
{
    Write-Output "VHD Data Disk, $NanoServerName-data-0.vhd, Successfully added to $NanoServerName."
}

If (!$?)
{
    Write-Output "Failed to add VHD Data Disk, $NanoServerName-data-0.vhd, to $NanoServerName."
    exit 2
}

# Starting the new Nano Server VM.
Start-VM `
    -ComputerName localhost `
    -VMName $NanoServerName `
    -Passthru

while ((Get-VM -Name $NanoServerName).Heartbeat -notmatch "OkApplicationsUnknown")
{
    Write-Output "$NanoServerName is not ready. Waiting 5 Seconds."
    Start-Sleep -Seconds 5
}

If ($?)
{
    Write-Output "VM $NanoServerName Started Successfully."
}

If (!$?)
{
    Write-Output "Failed to start VM $NanoServerName."
    exit 2
}

generate-self-signed-certificate.ps1

<#
.SYNOPSIS
This script generates a new Self-Signed Certificate on the Host it is ran from.

.DESCRIPTION
This script generates a new Self-Signed Certificate on the Host it is ran from. The Certificate will be marked as a CA by default.
Additionally, the Certificate will be added to the following Certificate Stores on the host it is ran from:
- LocalMachine\My
- Trusted Root Certificate Authorities for LocalMachine

This Script borrows heavily from the GitHub Gist created by @subTee: https://gist.github.com/subTee/b092cf7b46de222e82c0808270c431ad

Additional Documentation can be found on TechNet:

https://blogs.technet.microsoft.com/vishalagarwal/2009/08/21/generating-a-certificate-self-signed-using-powershell-and-certenroll-interfaces/

.PARAMETER CertSubject
The Value of the Certificate Subject Property.

.PARAMETER CertIssuer
The Value of the Certificate Issuer Property.

.PARAMETER CertValidFor
The length of time (Days) that the Certificate is set to be valid for.

.NOTES
Filename:   generate-self-signed-certificate.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

.EXAMPLE          
./generate-self-signed-certificate.ps1 `
-CertSubject "nanoservers.lumagate.com" `
-CertIssuer "Lumagate" `
-CertValidFor 365

#>

param
(
    [Parameter(Mandatory)]
    [String]$CertSubject,

    [Parameter(Mandatory)]
    [String]$CertIssuer,

    [Parameter(Mandatory)]
	[int]$CertValidFor
)

# Checking if a Certificate already exists on the Host.
$CertCheck = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -match $CertSubject })

If ($CertCheck)
{
	Write-Output "Certificate Found in the Certificate Store 'LocalMachine\My' with the Subject Value '$CertSubject'. Exiting."
	exit 1
}

If (!$CertCheck)
{
	Write-Output "No matching Certificate Found in the Certificate Store 'LocalMachine\My' with the Subject '$CertSubject'. Continuing."
}

# Creating Distinguished Name Property.
$DN = New-Object -ComObject "X509Enrollment.CX500DistinguishedName"
$DN.Encode( "CN=" + $CertSubject, $DN.X500NameFlags.X500NameFlags.XCN_CERT_NAME_STR_NONE)

# Creating Issuer Property.
$IssuerDN = New-Object -ComObject "X509Enrollment.CX500DistinguishedName"
$issuerDN.Encode("CN=" + $CertIssuer, $DN.X500NameFlags.X500NameFlags.XCN_CERT_NAME_STR_NONE)

# Create a new Private Key. 
$Key = New-Object -ComObject "X509Enrollment.CX509PrivateKey"
$Key.ProviderName =  "Microsoft Enhanced RSA and AES Cryptographic Provider" 
$Key.KeySpec = 2
$Key.Length = 2048
$Key.MachineContext = 1
$Key.Create() 

# Creating Certificate Attributes.
$ServerAuthObjectId = New-Object -ComObject "X509Enrollment.CObjectId"
$ServerAuthObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.1")
$EKUObjectIds = New-Object -ComObject "X509Enrollment.CObjectIds.1"
$EKUObjectIds.add($ServerAuthObjectId)
$EKUExt = New-Object -ComObject "X509Enrollment.CX509ExtensionEnhancedKeyUsage"
$EKUExt.InitializeEncode($EKUObjectIds)

# Adding Certificate Properties to the Certificate.
$Cert = New-Object -ComObject "X509Enrollment.CX509CertificateRequestCertificate"
$Cert.InitializeFromPrivateKey(2, $Key, "")
$Cert.Subject = $DN
$Cert.Issuer = $IssuerDN
$Cert.NotBefore = (get-date).AddDays(-1)
$Cert.NotAfter = $Cert.NotBefore.AddDays($CertValidFor)

# Using SHA256 Hash Function.
$HashAlgorithmObject = New-Object -ComObject X509Enrollment.CObjectId
$HashAlgorithmObject.InitializeFromAlgorithmName(1,0,0,"SHA256")
$Cert.HashAlgorithm = $HashAlgorithmObject

# Adding the Certificate Attributes to the Certificate.
$Cert.X509Extensions.Add($EKUExt)

# Adding Basic Constraints to the Certificate Authority Identifying the Certificate as a CA.
$BasicConstraints = New-Object -ComObject "X509Enrollment.CX509ExtensionBasicConstraints"
$BasicConstraints.InitializeEncode("true", 1)
$Cert.X509Extensions.Add($BasicConstraints)

# Creating the Certificate.
$Cert.Encode()

If ($?)
{
    Write-Output "Self-Signed Certificate generated Successfully."
	Start-Sleep -Seconds 2
}

If (!$?)
{
    Write-Output "Failed to generate the Self-Signed Certificate."
    Write-Output $Error[0].Exception
    exit 2
}

# Adding the Certificate into 'LocalMachine\My' Certificate Store.
$Enrollment = New-Object -ComObject "X509Enrollment.CX509Enrollment"
$Enrollment.InitializeFromRequest($Cert)
$CertData = $Enrollment.CreateRequest(0)
$Enrollment.InstallResponse(2, $CertData, 0, "")

If ($?)
{
    Write-Output "Self-Signed Certificate added to the 'LocalMachine\My' Certificate Store Successfully."
	Start-Sleep -Seconds 2
}

If (!$?)
{
    Write-Output "Failed to add the Self-Signed Certificate to the 'LocalMachine\My' Certificate Store."
    Write-Output $Error[0].Exception
    exit 2
}

# Retrieving the newly created Certificate. 
$Certificate = (Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Subject -match $CertSubject })

# Adding Certificate to the CA Root Certificate Store.
$StoreScope = "LocalMachine"
$StoreName  = "Root"
$Store      = New-Object System.Security.Cryptography.X509Certificates.X509Store $StoreName, $StoreScope
$Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$Store.Add($Certificate)							

If ($?)
{
    Write-Output "Self-Signed Certificate added to the 'Trusted Root Certificate Authorities' Certificate Store Successfully."
}

If (!$?)
{
    Write-Output "Failed to add the Self-Signed Certificate to the 'Trusted Root Certificate Authorities' Certificate Store."
    Write-Output $Error[0].Exception
    exit 2
}

# Retrieving the Certificate Thumbprint.
$CertThumbprint = $Certificate.Thumbprint

Write-Output "Certificate Thumbprint: $CertThumbprint"

get-nano-server-event-logs.ps1

<#

.SYNOPSIS
This script retrieves log file entries from Windows Event Logs based upon a specific timeframe in Days.

.DESCRIPTION
This script retrieves log file entries from Windows Event Logs based upon a specific timeframe in Days.

Once this script is launched, the following actions will occur:

- User will be prompted for the Password of the Username they are using to login to the Nano Server.
- PSCredential Object is created to login to the Nano Server.
- WMI call is made to the target Nano Server and the entries of the log file typed into the 'LogFile' Parameter
  are returned back.

.PARAMETER NanoServerName
The IP Address or the FQDN of the Nano Server.

.PARAMETER Username
This is either the Administrative Account of the Nano Server or a Domain Account with rights to login to the Nano Server.

.PARAMETER DomainName
The Domain Name of the Domain Account with rights to login to the Nano Server. i.e. - CONTOSO.

.PARAMETER LogFile
The Name of the Windows Event Log to Search, i.e. - Application, Security or System.

.PARAMETER Days
The Results to return back by (x) of Days, i.e. - 10 will return back all log entries from the previous 10 days.


.NOTES
Filename:   get-nano-server-event-logs.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

The WMI Query in this Script is a modified version from the following Microsoft TechNet article:

http://social.technet.microsoft.com/wiki/contents/articles/33324.nano-server-viewing-application-security-and-system-event-logs-using-wmi.aspx

.EXAMPLE
Retrieving the last 10 days of entries from the Application Log file on an  existing Nano Server joined to a Domain.

./get-nano-server-event-logs.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username serveradmin `
-DomainName lumagate.com `
-LogFile Application `
-Days 10

Retrieving the last 15 days of entries from the System Log file on an existing stand-alone Nano Server.

./get-nano-server-event-logs.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username administrator `
-LogFile System `
-Days 15

#>

param
(
   [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$Username,

    [String]$DomainName,

    [Parameter(Mandatory)]
    [String]$LogFile,

    [Parameter(Mandatory)]
    [String]$Days    
)

# Formatting the Credentials Username based on if the 'DomainName' Parameter was used or not.
If ($DomainName)
{
     $CredsUsername = "$DomainName\$Username"
}

If (!$DomainName)
{
     $CredsUsername = "~\$Username"
}

# Prompting the User to provide the Password for the Username they are using.
$CredsPassword = Read-Host -Prompt "Enter the Password for $CredsUsername" -AsSecureString

# Creating a PSCredential Object to login to the Nano Server.
$Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($CredsUsername,$CredsPassword)

# Retrieving and returning back the Log Entries from the Log File passed in from the 'LogFile' Parameter.
Get-WmiObject -ComputerName $NanoServerName `
    -Class Win32_NTLogEvent `
    -Filter ("(logfile='$LogFile' " `
        + "AND (TimeWritten >'$([System.Management.ManagementDateTimeConverter]::ToDMTFDateTime((get-date).AddDays(-$Days)))'))") `
    -Credential $Creds

install-dotnet-core-1.0.0-on-nano-server.ps1

<#

.SYNOPSIS
This script downloads and installs .NET Core 1.0.0 to 'C:\dotnet' on a target Nano Server.

.DESCRIPTION
This script downloads and installs .NET Core 1.0.0 to 'C:\dotnet' on a target Nano Server.

Once this script is launched, the following actions will occur:

- A new PowerShell Session will be created targeting the Nano Server.
    - Prompt will appear to login to the targeted Nano Server using the 'Username' Parameter.
- Check to see if .NET Core 1.0.0 is already installed. If it is found, script will exit.
- .NET Core 1.0.0 will be downloaded from https://go.microsoft.com/fwlink/?LinkID=809115 to C:\Windows\Temp.
- .NET Core 1.0.0 will be extracted to C:\dotnet.
- The PowerShell Session will be removed and Results of the process returned to STDOUT.

.PARAMETER NanoServerName
The IP Address or the FQDN of the Nano Server.

.PARAMETER Username
This is either the Administrative Account of the Nano Server or a Domain Account with rights to login to the Nano Server.

.PARAMETER DomainName
The Domain Name of the Domain Account with rights to login to the Nano Server. i.e. - CONTOSO.

.NOTES
Filename:   install-dotnet-core-1.0.0-on-nano-server.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

The main portion of the .NET Core Installation in this Script is a modified version from the following Microsoft Documentation article:

https://docs.microsoft.com/en-us/aspnet/core/tutorials/nano-server

.EXAMPLE
Installing .NET Core 1.0.0 to an existing Nano Server joined to a Domain.

./install-dotnet-core-1.0.0-on-nano-server.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username serveradmin `
-DomainName lumagate.com

Installing .NET Core 1.0.0 to an existing stand-alone Nano Server.

./install-dotnet-core-1.0.0-on-nano-server.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username administrator

#>

param
(
   [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$Username,

    [String]$DomainName
)

# Starting the WinRM Service if it isn't already Running.
If ((Get-Service | Where-Object {$_.Name -match "WinRM"}).Status -ne "Running")
{
    Start-Service -Name "WinRM"

    If ($?)
    {
        Write-Output "WinRM Service Started on Localhost."
    }

    If (!$?)
    {
        Write-Output "Failed to Start WinRM Service on Localhost."
        exit 2
    }
}

# Checking to see if the Public IP FQDN or Address of the Nano Server already exists in TrustedHosts.
If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -match $NanoServerName)
{
    Write-Output "$NanoServerName already exists in TrustedHosts."
}

If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -notmatch $NanoServerName)
{
    Write-Output "$NanoServerName was not found in TrustedHosts."

    # Adding The Public IP FQDN or Address of the Nano Server to TrustedHosts.
    Set-Item WSMan:\localhost\Client\TrustedHosts $NanoServerName -Concatenate -Force

    If ($?)
    {
        Write-Output "$NanoServerName was Successfully added to TrustedHosts in the WS-Management Configuration."
    }

    If (!$?)
    {
        Write-Output "Failed to add $NanoServerName to TrustedHosts in the WS-Management Configuration."
    }
}

If ($DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Domain Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential $DomainName\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError        

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Domain Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Domain Credentials."
        Write-Output $PSSessionError.Exception
        exit 2        
    } 
}

If (!$DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Local Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential ~\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Local Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Local Credentials."
        Write-Output $PSSessionError.Exception
        exit 2
    } 
}


# Connecting to the PowerShell Session and updating the Nano Server.
$Results = Invoke-Command `
    -Session $Session `
    -ScriptBlock {param($NanoServerName)

    # Determining if .NET Core 1.0.0 is installed on the Nano Server.
    Get-Item -Path "C:\dotnet\shared\Microsoft.NETCore.App\1.0.0" -ErrorAction SilentlyContinue -ErrorVariable CoreCheck

    If (!$CoreCheck)
    {
        $CoreCheckResult = ".NET Core 1.0.0 is already installed on $NanoServerName."
    }

    If ($CoreCheck)
    {
        $CoreCheckResult = ".NET Core 1.0.0 was not found on $NanoServerName."

        $SourcePath = "https://go.microsoft.com/fwlink/?LinkID=809115"
        $DestinationPath = "C:\dotnet"

        $EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId

        if (($EditionId -eq "ServerStandardNano") -or
            ($EditionId -eq "ServerDataCenterNano") -or
            ($EditionId -eq "NanoServer") -or
            ($EditionId -eq "ServerTuva")) 
            {
                $TempPath = [System.IO.Path]::GetTempFileName()
                if (($SourcePath -as [System.URI]).AbsoluteURI -ne $null)
                {
                    $handler = New-Object System.Net.Http.HttpClientHandler
                    $client = New-Object System.Net.Http.HttpClient($handler)
                    $client.Timeout = New-Object System.TimeSpan(0, 30, 0)
                    $cancelTokenSource = [System.Threading.CancellationTokenSource]::new()
                    $responseMsg = $client.GetAsync([System.Uri]::new($SourcePath), $cancelTokenSource.Token)
                    $responseMsg.Wait()
                    if (!$responseMsg.IsCanceled)
                    {
                        $response = $responseMsg.Result
                        if ($response.IsSuccessStatusCode)
                        {
                            $downloadedFileStream = [System.IO.FileStream]::new($TempPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
                            $copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream)
                            $copyStreamOp.Wait()
                            $downloadedFileStream.Close()
                            if ($copyStreamOp.Exception -ne $null)
                            {
                                $DownloadErrorMessage = $copyStreamOp.Exception
                            }
                        }
                    }
                }
                else
                {
                    $CopyErrorMessage = "Cannot copy from $SourcePath."
                }

                [System.IO.Compression.ZipFile]::ExtractToDirectory($TempPath, $DestinationPath)
                
                if ($?)
                {
                    $ExtractionResult = "Successfully extracted .NET Core 1.0.0 to 'C:\dotnet'"
                    Remove-Item $TempPath
                }

                if (!$?)
                {
                    $ExtractionResult = $copyStreamOp.Exception
                    Remove-Item $TempPath
                }
            }
        }

        # Adding Results of Configuration to a new PSCustomObject that will be outputted in the $Results Variable.
        New-Object PSObject `
        -Property @{
            CoreCheckResult                 = $CoreCheckResult
            DownloadErrorMessage            = $DownloadErrorMessage
            CopyErrorMessage                = $CopyErrorMessage
            ExtractionResult                = $ExtractionResult          
        }
    } -ArgumentList $NanoServerName

# Removing the PowerShell Session.
Remove-PSSession -Session $Session

# Returned Results.
$Results.CoreCheckResult
$Results.DownloadErrorMessage
$Results.CopyErrorMessage
$Results.ExtractionResult

install-dotnet-core-1.0.1-on-nano-server.ps1

<#

.SYNOPSIS
This script downloads and installs .NET Core 1.0.1 to 'C:\dotnet' on a target Nano Server.

.DESCRIPTION
This script downloads and installs .NET Core 1.0.1 to 'C:\dotnet' on a target Nano Server.

Once this script is launched, the following actions will occur:

- A new PowerShell Session will be created targeting the Nano Server.
    - Prompt will appear to login to the targeted Nano Server using the 'Username' Parameter.
- Check to see if .NET Core 1.0.1 is already installed. If it is found, script will exit.
- .NET Core 1.0.1 will be downloaded from https://go.microsoft.com/fwlink/?LinkID=827537 to C:\Windows\Temp.
- .NET Core 1.0.1 will be extracted to C:\dotnet.
- The PowerShell Session will be removed and Results of the process returned to STDOUT.

.PARAMETER NanoServerName
The IP Address or the FQDN of the Nano Server.

.PARAMETER Username
This is either the Administrative Account of the Nano Server or a Domain Account with rights to login to the Nano Server.

.PARAMETER DomainName
The Domain Name of the Domain Account with rights to login to the Nano Server. i.e. - CONTOSO.

.NOTES
Filename:   install-dotnet-core-1.0.1-on-nano-server.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

The main portion of the .NET Core Installation in this Script is a modified version from the following Microsoft Documentation article:

https://docs.microsoft.com/en-us/aspnet/core/tutorials/nano-server

.EXAMPLE
Installing .NET Core 1.0.1 to an existing Nano Server joined to a Domain.

./install-dotnet-core-1.0.1-on-nano-server.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username serveradmin `
-DomainName lumagate.com

Installing .NET Core 1.0.1 to an existing stand-alone Nano Server.

./install-dotnet-core-1.0.1-on-nano-server.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username administrator

#>

param
(
   [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$Username,

    [String]$DomainName
)

# Starting the WinRM Service if it isn't already Running.
If ((Get-Service | Where-Object {$_.Name -match "WinRM"}).Status -ne "Running")
{
    Start-Service -Name "WinRM"

    If ($?)
    {
        Write-Output "WinRM Service Started on Localhost."
    }

    If (!$?)
    {
        Write-Output "Failed to Start WinRM Service on Localhost."
        exit 2
    }
}

# Checking to see if the Public IP FQDN or Address of the Nano Server already exists in TrustedHosts.
If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -match $NanoServerName)
{
    Write-Output "$NanoServerName already exists in TrustedHosts."
}

If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -notmatch $NanoServerName)
{
    Write-Output "$NanoServerName was not found in TrustedHosts."

    # Adding The Public IP FQDN or Address of the Nano Server to TrustedHosts.
    Set-Item WSMan:\localhost\Client\TrustedHosts $NanoServerName -Concatenate -Force

    If ($?)
    {
        Write-Output "$NanoServerName was Successfully added to TrustedHosts in the WS-Management Configuration."
    }

    If (!$?)
    {
        Write-Output "Failed to add $NanoServerName to TrustedHosts in the WS-Management Configuration."
    }
}

If ($DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Domain Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential $DomainName\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError        

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Domain Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Domain Credentials."
        Write-Output $PSSessionError.Exception
        exit 2        
    } 
}

If (!$DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Local Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential ~\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Local Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Local Credentials."
        Write-Output $PSSessionError.Exception
        exit 2
    } 
}


# Connecting to the PowerShell Session and updating the Nano Server.
$Results = Invoke-Command `
    -Session $Session `
    -ScriptBlock {param($NanoServerName)

    # Determining if .NET Core 1.0.1 is installed on the Nano Server.
    Get-Item -Path "C:\dotnet\shared\Microsoft.NETCore.App\1.0.1" -ErrorAction SilentlyContinue -ErrorVariable CoreCheck

    If (!$CoreCheck)
    {
        $CoreCheckResult = ".NET Core 1.0.1 is already installed on $NanoServerName."
    }

    If ($CoreCheck)
    {
        $CoreCheckResult = ".NET Core 1.0.1 was not found on $NanoServerName."

        $SourcePath = "https://go.microsoft.com/fwlink/?LinkID=827537"
        $DestinationPath = "C:\dotnet"

        $EditionId = (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'EditionID').EditionId

        if (($EditionId -eq "ServerStandardNano") -or
            ($EditionId -eq "ServerDataCenterNano") -or
            ($EditionId -eq "NanoServer") -or
            ($EditionId -eq "ServerTuva")) 
            {
                $TempPath = [System.IO.Path]::GetTempFileName()
                if (($SourcePath -as [System.URI]).AbsoluteURI -ne $null)
                {
                    $handler = New-Object System.Net.Http.HttpClientHandler
                    $client = New-Object System.Net.Http.HttpClient($handler)
                    $client.Timeout = New-Object System.TimeSpan(0, 30, 0)
                    $cancelTokenSource = [System.Threading.CancellationTokenSource]::new()
                    $responseMsg = $client.GetAsync([System.Uri]::new($SourcePath), $cancelTokenSource.Token)
                    $responseMsg.Wait()
                    if (!$responseMsg.IsCanceled)
                    {
                        $response = $responseMsg.Result
                        if ($response.IsSuccessStatusCode)
                        {
                            $downloadedFileStream = [System.IO.FileStream]::new($TempPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
                            $copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream)
                            $copyStreamOp.Wait()
                            $downloadedFileStream.Close()
                            if ($copyStreamOp.Exception -ne $null)
                            {
                                $DownloadErrorMessage = $copyStreamOp.Exception
                            }
                        }
                    }
                }
                else
                {
                    $CopyErrorMessage = "Cannot copy from $SourcePath."
                }

                [System.IO.Compression.ZipFile]::ExtractToDirectory($TempPath, $DestinationPath)
                
                if ($?)
                {
                    $ExtractionResult = "Successfully extracted .NET Core 1.0.1 to 'C:\dotnet'"
                    Remove-Item $TempPath
                }

                if (!$?)
                {
                    $ExtractionResult = $copyStreamOp.Exception
                    Remove-Item $TempPath
                }
            }
        }

        # Adding Results of Configuration to a new PSCustomObject that will be outputted in the $Results Variable.
        New-Object PSObject `
        -Property @{
            CoreCheckResult                 = $CoreCheckResult
            DownloadErrorMessage            = $DownloadErrorMessage
            CopyErrorMessage                = $CopyErrorMessage
            ExtractionResult                = $ExtractionResult          
        }
    } -ArgumentList $NanoServerName

# Removing the PowerShell Session.
Remove-PSSession -Session $Session

# Returned Results.
$Results.CoreCheckResult
$Results.DownloadErrorMessage
$Results.CopyErrorMessage
$Results.ExtractionResult

update-nano-server-iis-for-dotnet-core-apps.ps1

<#

.SYNOPSIS
This script updates the IIS Configuration to work with .NET Core on a target Nano Server.

.DESCRIPTION
This script updates the IIS Configuration to work with .NET Core on a target Nano Server.

Once this script is launched, the following actions will occur:

- A new PowerShell Session will be created targeting the Nano Server.
    - Prompt will appear to login to the targeted Nano Server using the 'Username' Parameter.
- Check to see if IIS has already been updated on the Nano Server. If the update is found, script will exit.
- IIS Configuration is updated to work with .NET Core.
- The PowerShell Session will be removed and Results of the process returned to STDOUT.

.PARAMETER NanoServerName
The IP Address or the FQDN of the Nano Server.

.PARAMETER Username
This is either the Administrative Account of the Nano Server or a Domain Account with rights to login to the Nano Server.

.PARAMETER DomainName
The Domain Name of the Domain Account with rights to login to the Nano Server. i.e. - CONTOSO.

.NOTES
Filename:   update-nano-server-iis-for-dotnet-core-apps.ps1
Author:     Ryan Irujo (https://github.com/starkfell)
Language:   PowerShell 5.0

The section in this Script for updating the IIS Configuration on the Nano Server is a modified version from the following Microsoft Documentation article:

https://docs.microsoft.com/en-us/aspnet/core/tutorials/nano-server

.EXAMPLE
Updating the IIS Configuration on an existing Nano Server joined to a Domain.

./update-nano-server-iis-for-dotnet-core-apps.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username serveradmin `
-DomainName lumagate.com

Updating the IIS Configuration on an existing stand-alone Nano Server.

./update-nano-server-iis-for-dotnet-core-apps.ps1 `
-NanoServerName nanosrv-vm-5 `
-Username administrator

#>

param
(
   [Parameter(Mandatory)]
    [String]$NanoServerName,

    [Parameter(Mandatory)]
    [String]$Username,

    [String]$DomainName
)

# Starting the WinRM Service if it isn't already Running.
If ((Get-Service | Where-Object {$_.Name -match "WinRM"}).Status -ne "Running")
{
    Start-Service -Name "WinRM"

    If ($?)
    {
        Write-Output "WinRM Service Started on Localhost."
    }

    If (!$?)
    {
        Write-Output "Failed to Start WinRM Service on Localhost."
        exit 2
    }
}

# Checking to see if the Public IP FQDN or Address of the Nano Server already exists in TrustedHosts.
If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -match $NanoServerName)
{
    Write-Output "$NanoServerName already exists in TrustedHosts."
}

If((Get-Item WSMan:\localhost\Client\TrustedHosts).Value -notmatch $NanoServerName)
{
    Write-Output "$NanoServerName was not found in TrustedHosts."

    # Adding The Public IP FQDN or Address of the Nano Server to TrustedHosts.
    Set-Item WSMan:\localhost\Client\TrustedHosts $NanoServerName -Concatenate -Force

    If ($?)
    {
        Write-Output "$NanoServerName was Successfully added to TrustedHosts in the WS-Management Configuration."
    }

    If (!$?)
    {
        Write-Output "Failed to add $NanoServerName to TrustedHosts in the WS-Management Configuration."
    }
}

If ($DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Domain Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential $DomainName\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Domain Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Domain Credentials."
        Write-Output $PSSessionError.Exception
        exit 2
    }
}

If (!$DomainName)
{
    # Creating a new PowerShell Session to connect to the Nano Server using Local Credentials.
    $Session = New-PSSession `
        -ComputerName $NanoServerName `
        -Credential ~\$Username `
        -ErrorAction SilentlyContinue `
        -ErrorVariable PSSessionError

    If (!$PSSessionError)
    {
        Write-Output "New PowerShell Session created Successfully using Local Credentials."
    }

    If ($PSSessionError)
    {
        Write-Output "Failed to create a new PowerShell Session using Local Credentials."
        Write-Output $PSSessionError.Exception
        exit 2
    }
}

# Connecting to the PowerShell Session and updating the Nano Server.
$Results = Invoke-Command `
    -Session $Session `
    -ScriptBlock {param($NanoServerName)

    # Determining if the IIS Configuration on the Nano Server has been updated.
    Get-Item `
        -Path "C:\Windows\System32\inetsrv\config\applicationHost_AfterInstallingANCM.config" `
        -ErrorAction SilentlyContinue `
        -ErrorVariable FileCheck

    If (!$FileCheck)
    {
        $FileCheckResult = "IIS Configuration updates for IIS found on $NanoServerName."
    }

    If ($FileCheck)
    {
        $FileCheckResult = "IIS Configuration updates for IIS not found on $NanoServerName."

        # Backup existing applicationHost.config
        copy C:\Windows\System32\inetsrv\config\applicationHost.config C:\Windows\System32\inetsrv\config\applicationHost_BeforeInstallingANCM.config

        Import-Module IISAdministration

        # Initialize variables
        $aspNetCoreHandlerFilePath="C:\windows\system32\inetsrv\aspnetcore.dll"
        Reset-IISServerManager -confirm:$false
        $sm = Get-IISServerManager

        # Add AppSettings section
        $sm.GetApplicationHostConfiguration().RootSectionGroup.Sections.Add("appSettings")

        # Set Allow for handlers section
        $appHostconfig = $sm.GetApplicationHostConfiguration()
        $section = $appHostconfig.GetSection("system.webServer/handlers")
        $section.OverrideMode="Allow"

        # Add aspNetCore section to system.webServer
        $sectionaspNetCore = $appHostConfig.RootSectionGroup.SectionGroups["system.webServer"].Sections.Add("aspNetCore")
        $sectionaspNetCore.OverrideModeDefault = "Allow"
        $sm.CommitChanges()

        # Configure globalModule
        Reset-IISServerManager -confirm:$false
        $globalModules = Get-IISConfigSection "system.webServer/globalModules" | Get-IISConfigCollection
        New-IISConfigCollectionElement $globalModules -ConfigAttribute @{"name"="AspNetCoreModule";"image"=$aspNetCoreHandlerFilePath}

        # Configure module
        $modules = Get-IISConfigSection "system.webServer/modules" | Get-IISConfigCollection
        New-IISConfigCollectionElement $modules -ConfigAttribute @{"name"="AspNetCoreModule"}

        # Backup existing applicationHost.config
        copy C:\Windows\System32\inetsrv\config\applicationHost.config C:\Windows\System32\inetsrv\config\applicationHost_AfterInstallingANCM.config

        If ($?)
        {
            $IISUpdateResult = "Successfully updated IIS Configuration on $NanoServerName"
        }

        If (!$?)
        {
            $IISUpdateResult = "Failed to update IIS Configuration on $NanoServerName"
        }        
    }

    # Adding Results of Configuration to a new PSCustomObject that will be outputted in the $Results Variable.
    New-Object PSObject `
       -Property @{
        FileCheckResult                 = $FileCheckResult
        IISUpdateResult                 = $IISUpdateResult
        }
    } -ArgumentList $NanoServerName

# Removing the PowerShell Session.
Remove-PSSession -Session $Session

# Returned Results.
$Results.FileCheckResult
$Results.IISUpdateResult