2005 2006 2007 2008 2009 2010 2011 2015 2016 2017 aspnet azure csharp debugging elasticsearch exceptions firefox javascriptajax linux llblgen mongodb powershell projects python security services silverlight training videos wcf wpf xag xhtmlcss

Full SSL, HOSTS, and IIS Dev Setup via PowerShell

I'm all about people having their own setup and way of doing things. If you like Resharper, cool. If you like Notepad++, fine. Problems arise when you are forced into a standardized setup. Focus on the interface, not the implementation. With standardization you lose the natural QA you get from diverse environments.

Getting to specifics: HTTP is great, but times have changed. While we all know and love it (whatever), it needs to be taken out back. You can throw a site into the wild without protection from the beasts. This is NOT an after-the-fact production implementation detail. You need this in development. You can't have HTTPS surprises at the last second. That's simply irresponsible.

You should use IISExpress with SSL where possible. My preference is definitely for a full-IIS dev box setup with full HTTPS on everywhere.

What goes into this setup?

  • Dev IP Addresses
  • Updating HOSTS/DNS
  • SSL Certs
  • WebSites in IIS
  • Getting Visual Studio to like it

There is all where PowerShell comes in.

Prerequisite: Make sure you can run ps1 files in PowerShell. Here's one suggestion: Set-ExecutionPolicy RemoteSigned.

Adding IP addresses and updating HOSTS

While I sometimes teach kids, this explanation is for adults. I'll assume you can read and can deduce what does what. Code is self-documenting.

Do this in PowerShell as Administrator. I'd recommend the ISE.

10..30 | % {
    $ip = "10.1.111.$_"
    New-NetIPAddress -InterfaceAlias "Ethernet" -IPAddress $ip -PrefixLength 16
    #++ for Hyper-V
    #New-NetIPAddress -InterfaceAlias "vEthernet (External Virtual Switch)" -IPAddress $ip -PrefixLength 16
}

"
10.1.111.13 api.domain.local
10.1.111.12 viewer.domain.local
10.1.111.11 admin.domain.local
10.1.111.10 domain.local
" | ac "$($env:systemroot)\system32\drivers\etc\hosts"

Obviously, this will add the following IP Addresses and assign them .local domain names in the hosts file (don't hijack a top level domain; use .local!)

Next...

Creating SSL certs

Below is a PowerShell script that will:

  • create a master certificate and
  • create SSL certificates 1.

First, it uses makecert.exe to create the cert 2. Then, it uses pvk2pfx.exe to create a pfx file. Finally, it uses Import-PfxCertificate to import this pfx file into the certificate store.

Look at the "##+ run" section for examples of how to run this:

  • First, create a master certificate.
  • Then, create the others.

Uncomment and comment as needed. It just makes life easier. No need for fancy PowerShell modules.

Troubleshooting: Go to run (Win-R) and type MMC to get to a place where you can delete the certs. File->Add/Remove Snap-in. Add "Certificates". Add. Select Local Computer. Look in Trusted Root Certificate Authorities for master and Personal for other others. Use your eyes. You'll see it.

$virtualenv = {

##+ environment
$apiFolder = 'C:\Program Files (x86)\Windows Kits\10\bin\x64'
$target = 'E:\Drive\Code\Security\Cert'

if(!(Test-Path $apiFolder)) {
    "$apiFolder not found; check SDK installation"
    exit
}

##+ /environment

$root = {
    param([string]$name)

    $certFile = Join-Path $target $name
    $rootBaseName = "$certFile`Root"

    if(Test-Path "$rootBaseName.cer") {
        "$rootBaseName.cer already exists. ABORTING."
        exit
    }

    &"$apiFolder\makecert.exe" -r -n "CN=$rootName`Root" -pe -sv "$rootBaseName.pvk" -a sha1 -len 2048 -b 01/01/2010 -e 01/01/2030 -cy authority "$rootBaseName.cer"
    &"$apiFolder\pvk2pfx.exe" -pvk "$rootBaseName.pvk" -spc "$rootBaseName.cer" -pfx "$rootBaseName.pfx"

    Import-Certificate -FilePath "$rootBaseName.cer" -CertStoreLocation Cert:\LocalMachine\Root > $null
}

$ssl = {
    param([string]$name, [string]$rootName, [boolean]$isClientCert = $false)

    $rootCertFile = Join-Path $target $rootName
    $rootBaseName = "$rootCertFile`Root"

    if(!(Test-Path "$rootBaseName.cer")) {
        "$rootBaseName.cer does not exist. Stopping."
        return
    }

    $siteBaseName = Join-Path $target $name
    $siteBaseName

    if($isClientCert) {
        &"$apiFolder\makecert.exe" -iv "$rootBaseName.pvk" -ic "$rootBaseName.cer" -n "CN=$name" -pe -sv "$siteBaseName.pvk" -a sha1 -len 2048 -b 01/01/2010 -e 01/01/2030 -sky exchange "$siteBaseName.cer" -eku 1.3.6.1.5.5.7.3.2
    }
    else {
        &"$apiFolder\makecert.exe" -iv "$rootBaseName.pvk" -ic "$rootBaseName.cer" -n "CN=$name" -pe -sv "$siteBaseName.pvk" -a sha1 -len 2048 -b 01/01/2010 -e 01/01/2030 -sky exchange "$siteBaseName.cer" -eku 1.3.6.1.5.5.7.3.1
    }

    &"$apiFolder\pvk2pfx.exe" -pvk "$siteBaseName.pvk" -spc "$siteBaseName.cer" -pfx "$siteBaseName.pfx"

    ##++ cer is only public key; need private key for this one; that's pfx
    Import-PfxCertificate -FilePath "$siteBaseName.pfx" Cert:\LocalMachine\My > $null
}

##+ run
$rootName = 'Master'

#&$root $rootName
&$ssl 'identity.jampad.local' -rootName $rootName
#&$ssl 'devworkerrole01.local' -rootName $rootName
#&$ssl 'client.ssl' -rootName $rootName -isClientCert $true
##+ /run

}

&$virtualenv

Now for IIS...

Setting up IIS

If you are on Windows Server, just check go here and run that one line: IIS PowerShell Installation.

Now for setup...

This is somewhat of a monster: it creates the site, adds the IP addresses, and adds the certs. IIS doesn't let you create a binding-less WebSite, so HTTP came first. Remove it. It's obsolete and shouldn't be used.

$virtualenv = {

$networkInterfaceName = 'Ethernet'
##+ hyper-v
#$networkInterfaceName = 'vEthernet (External Virtual Switch)'

$ipCheck = {
    param([string]$ip)
    $addressData = Get-NetIPAddress | where { $_.InterfaceAlias -eq $networkInterfaceName } | select -expand IPAddress

    if(($addressData | where { $_ -eq $ip }).Count -eq 0) {
        Write-Host "IP address $ip not found. Run the following command. ABORTING."
        Write-Host "    New-NetIPAddress -InterfaceAlias '$networkInterfaceName' -IPAddress $ip -PrefixLength 16"
        exit
    }
}

$addSslBinding = {
    param([string]$name, [string]$ip, [string] $sslCert)
    if((Get-WebBinding  -name $name -IP $ip -Port 443 -Protocol https).Count -eq 0) {
        Write-Host "Adding SSL for address $ip..."

        New-WebBinding -name $name -IP $ip -Port 443 -Protocol https

        try {
            Write-Host "Setting SSL cert $sslCert..."
            $subjectName = "CN=$sslCert"
            $cert = ls Cert:\LocalMachine\My | where { $_.Subject -eq "CN=$sslCert" }
            $cert | ni "IIS:\SslBindings\$ip!443" > $null
        }
        catch {
            Write-Host ("`t{0}" -f $_.Exception.Message)
        }
    }
}

$setup = {
    param (
        [Parameter(Mandatory=$True)]
        [string] $name,
        [string] $path,
        [string] $ip,
        [int32] $httpPort = 80,
        [string] $sslCert,
        $ipHostMap,
        [boolean] $assignSslForEachHost,
        [boolean] $sslOnly
    )

    if($ip) {
        &$ipCheck $ip
    }
    elseif($ipHostMap) {
        foreach($ip in $ipHostMap.Keys) {
            # Write-Host ("$ip => {0}" -f $ipHostMap[$ip])
            &$ipCheck $ip
        }
        $ip = '127.0.0.1'
        $tempIp = $true
    }

    try {
        if((ls IIS:\AppPools | where name -eq $name).Count -eq 0) {
            Write-Host 'Creating application pool...'
            pushd IIS:\AppPools
            New-Item $name > $null
        }
        if((ls IIS:\Sites | where name -eq $name).Count -eq 0) {
            cd IIS:\Sites
            Write-Host "Creating web site ($name)..."
            Write-Host "`nNOTE: Adding temporary HTTP binding (required). If SSL-only, will try to remove HTTP in a moment.`n" -f Yellow
            New-Item $name -bindings @{protocol="http"; bindingInformation=$ip + ":" + $httpPort + ":"} -physicalPath $path > $null
            Set-ItemProperty $name -name applicationPool -value $name > $null
        }
        if($sslOnly -and !$sslCert) {
            $sslCert = $name
        }
        if($sslCert -and !$assignSslForEachHost) {
            &$addSslBinding $name $ip $sslCert
        }
        elseif($assignSslForEachHost) {
            foreach($ip in $ipHostMap.Keys) {
                $sslCert = $ipHostMap[$ip]
                &$addSslBinding $name $ip $sslCert
            }
        }
        if($sslOnly -and ($sslCert -or $ipHostMap)) {
            Write-Host "Removing HTTP..."
            Remove-WebBinding -Name $name -Protocol 'http'
            Write-Host "`nNOTE: You should double check to make sure HTTP was removed." -f Yellow
        }
    }
    finally {
        popd
    }
}

Import-Module WebAdministration

$ipHostMap = @{
  '10.1.111.13' = 'api.domain.local'
  '10.1.111.12' = 'viewer.domain.local'
  '10.1.111.11' = 'admin.domain.local'
  '10.1.111.10' = 'domain.local'
}

$name = 'domain.local'
if((ls IIS:\Sites | where name -eq $name).Count -gt 0) {
    cd IIS:\Sites
    Write-Host 'Deleting web site...'
    rm -r $name
}

# single-side multi-tenancy
&$setup -name 'domain.local' -path 'E:\_GIT\Domain.Project\Domain.Project.WebSite' -ipHostMap $ipHostMap -assignSslForEachHost $true -sslOnly $true


# single-side multi-tenancy
&$setup -name 'domain.local' -path 'E:\_GIT\Domain.Project\Domain.Project.WebSite' -ipHostMap $ipHostMap -assignSslForEachHost $true -sslOnly $true

&$setup -name 'anotherdomain.local' -ip '10.1.111.111' -path 'E:\_GIT\anotherdomain\anotherdomain.WebSite' -sslOnly $true
&$setup -name 'yetanotherdomain.local' -ip '10.1.111.112' -path 'E:\_GIT\yetanotherdomain\yetanotherdomain.WebSite' -sslOnly $true
&$setup -name 'another.local' -ip '10.1.111.113' -path 'E:\_GIT\another\another.WebSite' -sslOnly $true
&$setup -name 'onemore.local' -ip '10.1.111.113' -path 'E:\_GIT\onemore\onemore.WebSite' -sslOnly $true

}

&$virtualenv

Your question: "What the heck is that &$virtualenv?" I do everything in the ISE. It shares variables between tabs. This &$virtualenv runs the entire thing in it's own scope. The name isn't magic. I used to call it "scope". Whatever.

At this point the server is setup. Do this enough, it will take only the time it takes to type in the addresses and host names.

Removing annoying Visual Studio message

One problem: Visual Studio hates attaching to IIS. First, you have to be admin. Get over it. Be admin. I have my Visual Studio to always run as admin. Second, it complains when attaching to IIS.

Visual Studio Image

The fix is simple.

#vs 13
sp -path HKCU:\Software\Microsoft\VisualStudio\12.0\Debugger -Name DisableAttachSecurityWarning -Value 1

#vs 15
sp -path HKCU:\Software\Microsoft\VisualStudio\14.0\Debugger -Name DisableAttachSecurityWarning -Value 1

That's it. Now you have a nice server system.

Also, no more F5 nonsense. No more "run app". What "app"? It's not a console app. It's a WebSite. It's running anyway. F5 doesn't "start it". So... no more of that nonsense.

Quick HowTos

How do you debug Startup? Because Global.asax is obsolete and you're now using OWIN/Startup, you know that startup is Startup. Things now make sense. To debug this, throw in the following code:

System.Diagnostics.Debugger.Launch();

How do I restart the WebSite? Touch web.config (open and save it).

How do I restart the WebSite while attached? Yeah, for ABSOLUTELY NO REASON Visual Studio wants to detach when you touch web.config. The solution shouldn't be a surprise: PowerShell. When I'm developing I have a lot of PowerShell stuff open. Actually, when I'm doing anything I have it open.

Here's what I actually use to touch all my sites to reset them all at once. No F5-on-each. No AppPool recycle.

$touch = {
    param([string]$base)
    $config = Join-Path $base 'web.config'
    (ls $config).LastWriteTime = [DateTime]::Now
}

&$touch 'E:\_GIT\jampad.content\Content.Sample.WebSite'
&$touch 'E:\_GIT\jampad.content\Content.Sample.WebSite.Api'
&$touch 'E:\_GIT\jampad.content\Jampad.Content.WebSite.Api'
&$touch 'E:\_GIT\netfxharmonics\NetFXHarmonics.WebSite'


1
It will also create client certificates. See the 'client.ssl' example.
2
You can use pure PowerShell to create a self-signed certificate, but, apparently, makecert.exe goes above and beyond the call of duty.

Platonic and Aristotelian Data Philosophies

Software engineering is applied philosophy.

What does this mean when it comes to code and data?

Watch and see...

Quick Google Drive Install

So, you setup a new VM (locally or on Azure). How do you get your stuff on it?

Me? I use Google Drive. I sync up a custom folder named _Transport_SystemSetup. This folder has my Notepad2, WinRar, Chrome, and a ton of other thing I just dump into my Windows folder (so I can Win-R run them) (e.g. putty, makecert, nuget, all sysinternals).

Now... how do I get Google Drive on there? Chicken and Egg?

No. Run the following in PowerShell. It will download and install Google Drive for you.

$local = "$env:userprofile\downloads"
$url = 'https://dl.google.com/tag/s/appguid%3D%7B3C122445-AECE-4309-90B7-85A6AEF42AC0%7D%26iid%3D%7BD7394D9A-AF62-3D75-9686-627C62B1E926%7D%26lang%3Den%26browser%3D4%26usagestats%3D0%26appname%3DGoogle%2520Drive%26needsadmin%3Dtrue/drive/googledrivesync.exe'
$file = "$local\googledrivesync.exe"
wget $url -outfile $file

&$file

That's it.

Just for fun... sometimes I'll spin up an Azure VM just for a quick web test. So, here's the PowerShell to get Chrome quickly:

$local = "$env:userprofile\downloads"
$url = 'https://dl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7B01986938-8680-E7F2-FAFC-001798EF10B8%7D%26lang%3Den%26browser%3D4%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dprefers%26installdataindex%3Ddefaultbrowser/update2/installers/ChromeSetup.exe'
$file = "$local\ChromeSetup.exe"
wget $url -outfile $file

&$file

Hamlet: Better Random Strings

I don't like Lorem Ipsum. Why? Because I'm a student of Latin. Whenever I see the Lorem Ipsum nonsense, my brain spends more time trying to decode it than anything else. I know it's just randomized Cicero, but my brain tries to troll me.

So, I'm just going to use English. Instead of Lorem Ipsum, I use Hamlet. I took a section from Hamlet and threw it into PowerShell. Then, I optimized it down to just a few lines so I can reuse it in samples without much bloat:

By the way, Hamlet has 4752 distinct words with each inflection and declension being counted (e.g. "I", "be", "being", "was", and "am" being five counted as distinct words, though they really aren't).

$genData = 'o my offence is rank it smells to heaven hath the primal eldest curse upont a brothers murder pray can i not though inclination be as sharp will stronger guilt defeats strong intent and like man double business bound stand in pause where shall first begin both neglect what if this cursed hand were thicker than itself with blood there rain enough sweet heavens wash white snow whereto serves mercy but confront visage of whats prayer two-fold force forestalled ere we come fall or pardond being down then ill look up fault past form serve turn forgive me foul that cannot since am still possessd those effects for which did crown mine own ambition queen may one retain corrupted currents world offences gilded shove by justice oft tis seen wicked prize buys out law so above no shuffling action lies his true nature ourselves compelld even teeth forehead our faults give evidence rests try repentance can yet when repent wretched state bosom black death limed soul struggling free art more engaged help angels make assay bow stubborn knees heart strings steel soft sinews newborn babe all well'.split(' ')

$gen = {
    param ($count)
    1..$count | foreach {$r = @()} { $r += $genData[(random) % $genData.count] } {[String]::Join(' ', $r)}
}

&$gen 10

Don't care about Powershell? Chill. Python and JavaScript versions are at the end.

"like man teeth shall turn since repentance turn bound corrupted"

It's worth noting that if you run &$gen 100, you might be a candidate for some sort of poetry award:

so help intent rests a foul strong visage angels our double stubborn effects no angels did buys not law death i action since man oft is soul wash possessd which were we snow confront cursed shove forgive so up murder there offence repentance faults i but our forestalled black forgive man out serve look can defeats engaged down one mercy assay assay ere knees were retain gilded struggling a then prayer there law engaged prayer evidence of hand faults snow white a itself we business defeats serve double stand struggling smells defeats but repent heart both since defeats out shall

I should mention that if you wanted a concise way to capitalize the first letter, that's definitely doable:

$gen = {
    param ($count)
    1..$count | foreach {$a = @()} { $a += $genData[(random) % $genData.count] } {[String]::Join(' ', $a)}
}

All this works so far, but it's not very efficient.

Let's use our super-duper object-oriented, results-focused, enterprise-class benchmark software:

$then = [DateTime]::Now
&$gen 400
$now = [DateTime]::Now
Write-Host ($now - $then).Seconds

Our fancy benchmark enterprise-class reporting software shows 818ms on my machine.

What can we do better? Raw .NET:

if (!([System.Management.Automation.PSTypeName]'_netfxharmonics.hamlet.Generator').Type) {
    Add-Type -Language CSharp -TypeDefinition '
        namespace _Netfxharmonics.Hamlet {
            public static class Generator {
                private static readonly string[] Words = "o my offence is rank it smells to heaven hath the primal eldest curse upont a brothers murder pray can i not though inclination be as sharp will stronger guilt defeats strong intent and like man double business bound stand in pause where shall first begin both neglect what if this cursed hand were thicker than itself with blood there rain enough sweet heavens wash white snow whereto serves mercy but confront visage of whats prayer two-fold force forestalled ere we come fall or pardond being down then ill look up fault past form serve turn forgive me foul that cannot since am still possessd those effects for which did crown mine own ambition queen may one retain corrupted currents world offences gilded shove by justice oft tis seen wicked prize buys out law so above no shuffling action lies his true nature ourselves compelld even teeth forehead our faults give evidence rests try repentance can yet when repent wretched state bosom black death limed soul struggling free art more engaged help angels make assay bow stubborn knees heart strings steel soft sinews newborn babe all well".Split('' '');
                private static readonly int Length = Words.Length;
                private static readonly System.Random Rand = new System.Random();

                public static string Run(int count, bool subsequent = false) {
                    return Words[Rand.Next(1, Length)] + (count == 0 ? "" : " " + Run(count - 1, true));
                }
            }
        }

    '
}

You call this with:

[_netfxharmonics.ps.Generator]::Run(400)

Rerunning our fancy enterprise-class benchmark again...

$then = [DateTime]::Now
[_netfxharmonics.ps.Generator]::Run(400)
$now = [DateTime]::Now
Write-Host ($now - $then).Milliseconds

This time I get 8ms. Yeah. Eight.

Man, given this speed, we can increase our dictionary (see below to download hamlet_distinct.t):

if (!([System.Management.Automation.PSTypeName]'_netfxharmonics.hamlet.Generator').Type) {
    Add-Type -Language CSharp -TypeDefinition '
        namespace _Netfxharmonics.Hamlet {
            public static class Generator {
                private static readonly string[] Words = System.IO.File.ReadAllText(@"E:\Drive\Documents\Content\NetFX\NetFXContent\2015\03\hamlet\hamlet_distinct.t").Split('' '');
                private static readonly int Length = Words.Length;
                private static readonly System.Random Rand = new System.Random();

                public static string Run(int count, bool subsequent = false) {
                    return Words[Rand.Next(1, Length)] + (count == 0 ? "" : " " + Run(count - 1, true));
                }
            }
        }

    '
}

This needs to work in more than simply PowerShell. I do a lot of work in both Python and JavaScript, so...

Python

#!/usr/bin/env python

from random import randint

genData = 'o my offence is rank it smells to heaven hath the primal eldest curse upont a brothers murder pray can i not though inclination be as sharp will stronger guilt defeats strong intent and like man double business bound stand in pause where shall first begin both neglect what if this cursed hand were thicker than itself with blood there rain enough sweet heavens wash white snow whereto serves mercy but confront visage of whats prayer two-fold force forestalled ere we come fall or pardond being down then ill look up fault past form serve turn forgive me foul that cannot since am still possessd those effects for which did crown mine own ambition queen may one retain corrupted currents world offences gilded shove by justice oft tis seen wicked prize buys out law so above no shuffling action lies his true nature ourselves compelld even teeth forehead our faults give evidence rests try repentance can yet when repent wretched state bosom black death limed soul struggling free art more engaged help angels make assay bow stubborn knees heart strings steel soft sinews newborn babe all well'.split(' ')

def gen(count):
    return genData[randint(1, len(genData) - 1)] + ('' if count == 0 else ' ' + gen(count - 1))

print gen(300)

JavaScript

var genData = 'o my offence is rank it smells to heaven hath the primal eldest curse upont a brothers murder pray can i not though inclination be as sharp will stronger guilt defeats strong intent and like man double business bound stand in pause where shall first begin both neglect what if this cursed hand were thicker than itself with blood there rain enough sweet heavens wash white snow whereto serves mercy but confront visage of whats prayer two-fold force forestalled ere we come fall or pardond being down then ill look up fault past form serve turn forgive me foul that cannot since am still possessd those effects for which did crown mine own ambition queen may one retain corrupted currents world offences gilded shove by justice oft tis seen wicked prize buys out law so above no shuffling action lies his true nature ourselves compelld even teeth forehead our faults give evidence rests try repentance can yet when repent wretched state bosom black death limed soul struggling free art more engaged help angels make assay bow stubborn knees heart strings steel soft sinews newborn babe all well'.split(' ');

function gen(count) {
    return genData[parseInt(Math.random() * genData.length)] + (count == 0 ? '' : ' ' + gen(count - 1));
}

console.log(gen(300))

Want the full hamlet_distinct.t? OK, fine....

IIS PowerShell Installation

Need to install IIS on Windows Server 2012? Be awesome: use PowerShell.

The following PowerShell installs absolutely everything, which is what you want on a dev environment. Type Get-WindowsFeatures to get a list of options to tailor a nice command for production.

Add-WindowsFeature Web-Server -IncludeAllSubFeature -IncludeManagementTools