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

Arch Linux Setup

"Back in my day..."

...Linux was 50MB. The entire server. Today? Eleventy-billion GB.

Well, that's the story with what I call Noob Linux (aka Ubuntu). I wrote off RedHat for years because of the insanity of the size (multiple CDs in the 90s). Today Ubuntu is the new RedHat in that regard 1.

Debian didn't buy into the bloat entirely; it's still nice. Yet, for me, there's a new kid on the block that was able to release my Debian death-grip: Arch Linux.

It's powerful. It's simple to use. It has a proper level of complexity to allow customization. The levels of porcelain and plumbing are quite balanced. Modern "Microsoft stack" developers get this. This is what C# is all about. This is what Azure is all about.

Now, how does one setup Arch linux? Answer: manually. Once you get through this, you've effectively paid your dues.

I'll give some commentary along the way. I generally follow the motto: if you can't repeat it, you can't do it. "I did it once" means nothing (this is why skill > experience). Don't rely on luck or "HOLY CRAP IT WORKS. NOBODY TOUCH IT".

Prerequisites

You're going to be in the console. Linux uses a console. A GUI on Linux is not Linux. It's heresy. So, know these:

  • CTRL-A : beginning of line

  • CTRL-E : end of line

  • ESC then left-arrow : go back one word

  • ESC then right-arrow: go forward one word

  • If backspace gives you weird characters, hold CTRL while hitting backspace.

  • For passwords, use the numbers above your letters, not on the keypad. This might (donno) be the case in a few places.

You are not ready to be awesome.

Core Installation

When you first mount your disc and start your VM (it's a good assumption), you'll get a screen...

Arch Linux boot

...then a prompt. Let's run with that...

Arch Linux boot

First, let's setup our disk. This is the same thing you're going to do in Windows, but Linux is all about more control:

What disk do you want to play with? Let's find out:

# fdisk -l

Arch Linux fdisk -l

Note: this is for MBR. For GUID partitions, use gdisk.

See the /dev/sda and /dev/sdb stuff? Those are your disks: a and b (and c and d, etc...).

Look at the sizes deduce which one to use. You have to do the same on Windows. I've many disks. My boot SSD is much smaller than the others; that's how I know which disk to use.

Now, get into fdisk (it's a cli).

# fdisk /dev/sda

I'm going to demonstrate two different methods for the swap space. So skim both before playing with anything.

We want to create three partitions:

Regarding the 4GB swap partition, don't do the typical Microsoft developer "lol guidelines are for noooooooooooooooobs ima do what feels goodz. nobody has eeeeeeeever done this before so ima be a pioneerz" nonsense. Reference the size recommendations to see if 4G was right for you. RTFM! (that's reference the freggin manual-- nobody should ever read a manual like it's a Tolstoy novel)

Here are the commands in series: n, +500M, n, +4G, t, 2, 82, n, w.

You're going to use a lot of the defaults (e.g. partition number and start sector). Reference the following image sample:

Arch Linux create swap

Honestly, it's better if you play with this until it makes sense. Holding your hand won't help you in the long run. Learning comes from mistakes. Make those mistakes... and create VM snapshots.

"Commentary":

  #(MBR; use gdisk for GUID paritions)
  (250 to 500M for boot, then RAM-sized swap, then /)
  n
    (new partition)
    (enter=defaults)
  t
    (change type)
    (82 swap)
  w
    (write/save)

Alternatively, you could use gdisk (for GUID partitions). Same idea.

Now... let's format these (ext3 is fine too):

# format partition 3    
mkfs.ext4 /dev/sda3

# format partition 1
mkfs.ext4 /dev/sda1

We are skipping partition 2 for now. That's a swap partition. That a different paradigm entirely.

Next, we will mount these.

The big partition first...

mount /dev/sda3 /mnt

We mount this as /mnt not / because are currently in a system that using /. Windows splits the world up into separate drives via letter whereas Unix/Linux (hereafter just "Linux") systems use a unified naming system. In Windows you can change drive letters; in Linux you change mount points.

With this mounted, we will create a place for boot partition to mount:

mkdir /mnt/boot

Then mount the boot partition:

mount /dev/sda1 /mnt/boot

What about the 4GB partition? That's the swap partition. We treat it differently. We create the swap area then use it.

mkswap /dev/sda2
swapon /dev/sda2

Havinag said this, there's another way to handle swap space, a more modern-world / VM / dev environment compatible method: don't create an entire swap partition.

The alternative to a swap partition is a swap file. In this paradigm, don't create a swap partition. Just create your 500M boot partition and your *MB data partition.

This works here because we are using ext4. Do not try this with btrfs.

Then, instead of creating a 4G monster swap partition, let's create a smaller 512M swap file:

fallocate -l 512M /swp

Let's make it so that only the file owner (in this case, the file creator: root) can read and write it:

chmod 600 /swp

Now we can do our other commands:

mkswap /swp
swapon /swp

See https://wiki.archlinux.org/index.php/Swap for more information.

We are now at the point where we install the base system. This is where it's really cool: you choose what to install. Personally, I've a few things I want to install right away so I don't have to deal with them later:

Here's my full command 2 3:

pacstrap -i /mnt base base-devel grub-bios openssh sudo

This command also tell you something important: whether you are on the Internet or not.

This is:

  • Base system
  • Basic dev tools
  • Kernely stuff
  • Boot manager
  • OpenSSH server
  • sudo (to run commands as root)

In case you're curious, here's what's inside base:

Arch Linux pacstrap base

We're almost done with the system setup.

We need to tell the system about our mount points. There's a tool for that.

genfstab -U -p /mnt >> /mnt/etc/fstab

The -U is important. Instead of mapping to /dev/sda1, you're telling the system to use a GUID. This removes the risk of things moving around. See SOF for more information.

If you did did the swap file method instead of the swap partition method: you also need to run the following:

echo "/swp none swap defaults 0 0" >> /mnt/etc/fstab

This will save make sure that /swp is used on boot.

General Linux advice: during any setup always ask yourself if it needs to be something that starts on boot. Different different Linux distros will have different setups. As you'll see in a moment, Arch Linux follows the "systemctl enable X" method.

Now we need to do some stuff that presupposes your system is mounted at /. So, we need to change our root:

arch-chroot /mnt

Now /mnt is /.

Change your password:

passwd

Let's create a boot optimizer:

mkinitcpio -p linux

Magic voodoo? No. That doesn't exist. That's fiction. Well, ok, a technology advanced enough... is magic... or something.

So, you've special drivers needed to use your disk. Where are the drivers? On... the... disk. Lovely. Well... computer architecture to the rescue:

Note: this is only for MBR. More modern systems use entirely different mechanics.

When you turn on a computer, at some point the bios is going to know what disk you want to boot from (you know, that setting you set in the F2/F10/del screen). When it gives to your hard drive, it will look in the first 512 bytes for some type of instruction of what to do. This 512 byte area is the Master Boot Record.

It's a fun activity to play with this 512 byte area. You can write a quick assembler tool to have your own mini-OS. Here's a fun tutorial... Its uses 16-bit registers (e.g. AX, BX), so it's a fun flashback for many of us.

Anyway... Linux has this little image that mkinitcpio creates that contains the drivers 'n stuff needed to boot your system. See reddit for more info.

OK, let's use the boot loader to tell it how to boot:

grub-install /dev/sda

Now to save the related config:

grub-mkconfig -o /boot/grub/grub.cfg

We're done. Let's exit chroot, unmount, and reboot. Remember to unmount your ISO / remove disc.

exit
umount -R /mnt
reboot

Phase 2 Installation

Done? No. Sure you can boot, but you're about to login as root. Dude... no.

There's more to do, but I hate the idea of doing it on a console. In Hyper-V, you're not at a place where you can copy/paste. So, let's turn on networking and SSH into the server.

First, get the DHCP client doing something:

dhcpcd

You'll probably see some scary looking "no carrier" nonsense. Ignore it.

Wait a few seconds. Then try ip addr. No IP yet? Wait longer and run ip addr again.

Now what? Well, we SSH into our server. But... we never made a user. Yeah, so, I cheat...

Edit your /etc/ssh/sshd_config. Add PermitRootLogin yes.

You can do this one in command with the following:

echo "
PermitRootLogin yes
" >> /etc/ssh/sshd_config

This will allow root to SSH into your server.

We'll remove this a little later. Chill out.

Anyway, now to start OpenSSH:

systemctl restart sshd

Just so we don't forget, let's set OpenSSH to auto start on boot:

systemctl enable sshd

You can now SSH into your server as root. Do it.

How? Answer: get an SSH client. The popular one is putty. I can't stand putty. I do keep it in my Windows folder for quick access from Win-R, but only as a backup. I personally prefer SecureCRT. I definitely find it worth the money.

The rest of this is mostly copy/paste.

Let's create a user with the appropriate groups:

useradd -m -g users -G wheel,storage,power -s /bin/bash user01

Change password:

passwd user01

That command asks you for a password, so you're not going to be able to simply paste it with a bunch of other subsequent commands into SSH.

However, the rest of this can be copy/pasted in:

# sudo users
echo "%wheel ALL=(ALL) ALL" >> /etc/sudoers

# hostname
echo "arch" > /etc/hostname

# localization
echo LANG="en_US.UTF-8" >> /etc/locale.conf
echo LC_COLLATE="C" >> /etc/locale.conf
echo LC_TIME="en_US.UTF-8" >> /etc/locale.conf
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
export LANG=en_US.UTF-8
locale-gen

# clock
ln -sf /usr/share/zoneinfo/America/Chicago /etc/localtime
hwclock --systohc --utc

# name servers
echo "nameserver 8.8.8.8
nameserver 8.8.4.4" > /etc/resolv.conf

# makeflags; 8 cores
echo "export MAKEFLAGS='-j 8'" >> /etc/bash.bashrc

The first command tells sudo to allow anyone in the wheel group to be able to run root commands. What's with the wheel name? SOF has an answer/theory for that one.

The only other command that probably requires commentary is the MAKEFLAGS line. This tells gcc the system how many cores to use. Many installations require gcc, including modules installed with Python's pip. So, don't think you're off the hook.

Almost done. Exit SSH. Reconnect. Login as the new user.

If that works, you can disable root for SSH.

Don't skip this step.

I don't like to prefix all my commands with sudo, so I just run bash as root:

sudo bash

The following command will remove the ssh-as-root line:

sed -i '/PermitRootLogin yes/d' /etc/ssh/sshd_config

For more info on the awesome tool called sed, there's a great compilation called USEFUL ONE-LINE SCRIPTS FOR SED that you should definitely bookmark. Also, visit the parent site. It's mostly art nonsense, but the docs section has good things.

Then restart OpenSSH:

systemctl restart sshd

At this point I like to install a bunch of other stuff. We do this with pacman. This is like apt-get in the Debian world. Actually, it's more like a merger of apt-get, apt-cache, and other things.

Here's what's in my usual toolbox:

pacman -S mlocate wget gcc openssh make mercurial git bc curl vim dnsutils whois net-tools lsof --noconfirm 

Some of that is already installed (e.g. openssh), but nobody cares. The list is more for my memory than for pacman.

Networking (dhcp)

The main thing left to do is to get DHCP to run automatically on boot.

Let's see what our interfaces are:

ip link

You could also use the following to get your interfaces:

ls /sys/class/net

You'll find that various parts of the file-system aren't really files, but very similar to the Windows WMI interfaces.

I've lo and eth0, yours might be enp3s0.

Now to enable DHCP for that interface:

systemctl enable dhcpcd@eth0.service

You could simply do this for everything, but let's be specific (you might want to play with static on a different interface).

systemctl enable dhcpcd.service

Networking (static)

Let's try static IPs now. It's actually a bit easier than you read in the docs:

echo "Description='A basic static ethernet connection'
Interface=eth0
Connection=ethernet
IP=static
Address=('10.1.211.10/16' '10.1.211.11/16' '10.1.211.12/16' '10.1.211.13/16')
Gateway='10.1.1.254'
" > /etc/netctl/home-static

netctl enable home-static

1
RedHat is the new Windows for many people; e.g. enterprise-class $$. CentOS (think of it as a free version of RedHat), though, is pretty sweet. I'd recommend CentOS for production servers.
2
You might not need base-devel. Most of it is already in base. Whatever.
3
You might see pacstrap -i /mnt base base-devel linux in some docs. The linux package is in the base package. If you ran the command with "base base-devel", then ran it with "linux", you'd see a 0MB net upgrade.

Stop calling everything REST

Everyone needs to read the following again:

I'll be concise. We're all busy. Also, I'm lazy.

Not at the highest level? It's not REST. Stop calling it REST. It's probably web CRUD and RPC.

Use of Microsoft WebAPI does not christen thee "REST". Swagger does not transmute an API to REST1. Most importantly, calling it REST does not make it REST. Speech-act theory won't help you here. If it's not REST, calling it REST does nothing more than make you wrong.

Regarding Richardson Maturity Model and Roy Fielding's thesis (and blog), the steps are not levels of REST. These are steps to REST.

The journey is not REST. The destination is REST.

  • No hypermedia => No REST.

  • No HATEOAS => No REST.

Note the periods at the end of the previous two lines. They are key. PERIOD.

You might be RESTsome, but you aren't RESTful.

You might be RESTistic, but you aren't RESTastic. 2

Important endnote: this is about what you call REST. This doesn't mean REST is the answer. No document written by a single individual is binding de jure nor de facto. Checks and balances are required. There is no committee REST spec. In that regard, REST is fiction. Yet, I can't call a pizza a Smurf just because I feel like it.


1
In fact, using Swagger may actually prevent you from making a REST service.
2
In the sense that your image in the mirror is human-istic, but it doesn't have some human. It has NO human. Is is NO human.

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

Understanding WCF Versioning ("My Random Notes" Edition)

I never got around to putting the following into something pretty and eloquent... and might not blog any more, but here are my raw notes I would have used to write something more full.


WCF Service Contract Versioning

Clients calls a service interface, not an implemenation. It identifies a particular interface via the WCF namespace set on the service contract on the service. As long as the client calls an interface with a particular namespace on the service side, the call will be allowed.

Strong Versioning

For example, if a client is using the following service contract, it can only call this contract using its full XML namespace. The namespace is specified with the Namespace property of the ServiceContract attribute. However, the interface that is called is the specified namespace suffixed with the interface name. This gives a feel very similar to that of .NET's namespace system which allows for namespaces for organization and full types as the types namespace suffixed with the type name.

As an example, see the following interface is accessed via the "http://www.abccorp.com/services/sample01/2007/02/IServiceV1" identifier.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/01")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

If, however, the month portion of a namespace is changed from version 01 to 02, as the following example demonstrate, the client would immediately break as it would still be calling via the old identifier.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

On the service-side there is no "magic" that allows one service to be access over another. The client accesses a specific interface and that specific iterface is of course assigned as a WCF endpoint. Therefore, each version requires it's own endpoint. However, an endpoint doesn't mean another entire host, power, or base URI. In an HTTP environment, this simply means that the endpoint identifier is suffixed to the actual service URI.

For example, instead an address of "" (blank), the address is an arbitrary endpoint identifier as seen below:

Before

<endpoint address="" binding="basicHttpBinding" contract="IService" />

After

<endpoint address="v1" binding="basicHttpBinding" contract="IServiceV1" />
<endpoint address="v2" binding="basicHttpBinding" contract="IServiceV2" />

This will be mentioned in a bit more detail in a later section.

Lax Versioning

Assume the following service service contract, implementation, and endpoint:

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

public class Service : IServiceV1
{
    public String GetData(Int32 value) {
        return String.Format("You send {0}.", value);
    }
}

<endpoint address="v1" binding="wsHttpBinding" contract="IServiceV1" />

At this point, if the client gets the service metadata and creates a proxy, the client may send the following:

using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData(3));
}

Now, assume the service changes from Int32 to String with no version change.

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(String value);
}

public class Service : IServiceV1
{
    public String GetData(String value) {
        return String.Format("You send {0}.", value);
    }
}

With updating the service proxy on the client, the client is strongly typed to an Int32. Therefore, the client will not be able to send a String to the server. However, the client will still function as the service implicitly converts the value to a String.

The reverse of this situation can also be true. If the service started out using a String and was changed to use an Int32, the client, even though it is bound to a String type, can send data to the server as long as it is castable to an Int32. See the following for an example:

// This will work
using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData("4"));
}

// This will NOT work
using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData("four"));
}

Strong and Lax Together

A service may serve service contracts as the following demonstrates

public class Service : IServiceV1, IServiceV2
{
    // ...
}

The service contracts have the same essence, but their operation signatures will differ.

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV1
{
    [OperationContract]
    String GetData(Int32 value);
}

[ServiceContract(Namespace = "http://www.abccorp.com/services/sample01/2007/02")]
public interface IServiceV2
{
    [OperationContract]
    String GetData(Int32 value, String code);
}

In this case, the namespace is the same, but this is in no way a requirement. Any portion of the namespace can change as they are two completely different contracts.

These two contracts also share a method with the same name. This is legal in .NET, but there is no such thing as "service overloading". That is, the overloading is only in .NET and does not transfer to the service. Therefore, as has been previously mentioned, each contract must have its own endpoint. Given that each endpoint is for the same service, only the following is requires:


When a client will specify which endpoint to call when it creates its service proxy. For example,

using (ServiceV1Client client = new ServiceV1Client("WSHttpBinding_IServiceV1")) {
    Console.WriteLine(client.GetData(4));
}

using (ServiceV2Client client = new ServiceV2Client("WSHttpBinding_IServiceV2")) {
    Console.WriteLine(client.GetData(3, "hello"));
}

The client's application configuration will contain references to each endpoint. For example, this client may have the following:

<endpoint
  address="http://localhost:2352/Website/Service.svc/v1"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2" />

The client also needs a specific strongly typed proxy for each endpoint, but it is also trivial. Below is an example of a complete proxy; other proxies are created following the same pattern. The properties set in the attributes should be easy to comprehend. However, it should be explicitly stated that the ConfigurationName property of the ServiceContract attribute matches the contract stated in the client application configuration.

[ServiceContract(Namespace="http://www.abccorp.com/services/sample01/2007/02", ConfigurationName="ServiceReference1.IServiceV1")]
public interface IServiceV1 {
    
    [OperationContract(Action="http://www.abccorp.com/services/sample01/2007/02/IServiceV1/GetData", ReplyAction="http://www.abccorp.com/services/sample01/2007/02/IServiceV1/GetDataResponse")]
    String GetData(Int32 value);
}

public interface IServiceV1Channel : IServiceV1, IClientChannel {
}

public partial class ServiceV1Client : ClientBase, IServiceV1 {
    public ServiceV1Client() {
    }
    
    public ServiceV1Client(String endpointConfigurationName) : 
            base(endpointConfigurationName) {
    }

    public ServiceV1Client(String endpointConfigurationName, String remoteAddress) : 
            base(endpointConfigurationName, remoteAddress) {
    }

    public ServiceV1Client(String endpointConfigurationName, EndpointAddress remoteAddress) : 
            base(endpointConfigurationName, remoteAddress) {
    }
    
    public ServiceV1Client(Binding binding, EndpointAddress remoteAddress) : 
            base(binding, remoteAddress) {
    }

    public String GetData(Int32 value) {
        return base.Channel.GetData(value);
    }
}

Given the simplicity of WCF, there is no reason to ever use tools like Visual Studio or svcutil.exe for proxy or configuration creation. Also, be aware that not all of the constructors for the proxy ("ServiceV1Client") are required.

It should also be explicitly stated that nothing that nothing here forbids the use of different bindings on each of the endpoints. For example, the following are legal endpoints on the client and Server:

Server

>endpoint address="v1" binding="basicHttpBinding" contract="IServiceV1" /<
>endpoint address="v2" binding="wsHttpBinding" contract="IServiceV2" //<

Client

>endpoint address="http://localhost:2352/Website/Service.svc/v1" binding="basicHttpBinding" contract="ServiceReference1.IServiceV1" name="WSHttpBinding_IServiceV1" /<
>endpoint address="http://localhost:2352/Website/Service.svc/v2" binding="wsHttpBinding" contract="ServiceReference1.IServiceV2" name="WSHttpBinding_IServiceV2" /<
If there are multiple bindings (B) and multiple contracts (C), then there needs to be B*C endpoints. For example, you may have the following: Server:
<endpoint address="v1/basic" binding="basicHttpBinding" contract="IServiceV1" />
<endpoint address="v2/basic" binding="basicHttpBinding" contract="IServiceV2" />
<endpoint address="v3/basic" binding="basicHttpBinding" contract="IServiceV3" />
<endpoint address="v4/basic" binding="basicHttpBinding" contract="IServiceV4" />

<endpoint address="v1/wshttp" binding="wsHttpBinding" contract="IServiceV1" />
<endpoint address="v2/wshttp" binding="wsHttpBinding" contract="IServiceV2" />
<endpoint address="v3/wshttp" binding="wsHttpBinding" contract="IServiceV3" />
<endpoint address="v4/wshttp" binding="wsHttpBinding" contract="IServiceV4" />

Client

<endpoint
  address="http://localhost:2352/Website/Service.svc/v1/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v3/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV3"
  name="WSHttpBinding_IServiceV3_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v4/basic"
  binding="basicHttpBinding"
  contract="ServiceReference1.IServiceV4"
  name="WSHttpBinding_IServiceV4_Basic" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v1/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV1"
  name="WSHttpBinding_IServiceV1_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v2/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV2"
  name="WSHttpBinding_IServiceV2_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v3/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV3"
  name="WSHttpBinding_IServiceV3_WSHttp" />
<endpoint
  address="http://localhost:2352/Website/Service.svc/v4/wshttp"
  binding="wsHttpBinding"
  contract="ServiceReference1.IServiceV4"
  name="WSHttpBinding_IServiceV4_WSHttp" />

Notice that neither the service contract, the service, nor the client proxy know anything about the bindings or the endpoints.

WCF Data Contract Versioning

If a v2 service sends a populated Person object to a v1 client, then the client will see a series of default values with the actual values being in the ExtensionData object. If the client sets its own data and sends that to the server, these values are effectively ignored.

For example, if the v2 service sends the following v2 data to the v1 client, the v1 client will see null.

return new Person()
{
    FirstName = "John",
    LastName = "Doe",
    Address1 = "1827 North South Street",
    City = "Winchestertonfieldville",
    State = "IA",
    Zip = "50182",
    Id = id
};

If the v1 client then proceeds to populate the v1 object and send it to the v2 service, the v2 service will completely ignore everything the v1 client has set because it is setting v1 data that the v2 service knows nothing about. The following client data, for example, is meaningless:

Person p = client.GetPerson(3);
p.Address = "Blank";
p.FirstName = "Jane";
client.SavePerson(p);

The v2 service will simply receive the same data it had previously send. That is, FirstName will still say "John".

The v2 service will accept the data from the v1 client if the v1 client had previously obtained that data from a v2 service. This is true even if the v2 data contract has a newly added data member which has IsRequired set to true.

However, if the client attempts to send a new v1 object to the v2 service, which has a v2 contract with a newly added data member with IsRequired set to true, a service fault will be thrown.

Client Data Contract

The data contract on the service is the same as the data contract on the client. Therefore the following is usable on both ends:

[DataContract(Namespace = "http://www.abccorp.com/services/sample01/datacontracts/2007/02")]
public class Person
{
    [DataMember]
    public Int32 Id { get; set; }

    [DataMember]
    public String FirstName { get; set; }

    [DataMember]
    public String LastName { get; set; }

    [DataMember(IsRequired=true)]
    public String Address1 { get; set; }

    [DataMember]
    public String Address2 { get; set; }

    [DataMember]
    public String City { get; set; }

    [DataMember]
    public String State { get; set; }

    [DataMember]
    public String Zip { get; set; }
}

This means that if the service sends an instance of this data contract to the client and does not have the Address1 property, a fault will occur. Given that IsRequired is checked at the destination, the fault will occur immediately when the client requests the data from the service.

In order to obtain data that is not strongly typed in the data member, you must implement the IExtensibleDataObject interface. This allows data that is unknown at the destination to be encapulated in an object. This is usable on both the client and service end. The data will be placed into the ExtensionData object which is of type ExtensionDataObject.

For example, if the server adds more data members to its data contract than what the client expects, the client will obviously not be able to populate this data into any local object. However, if the client data contract implements the IExtensibleDataObject interface, then any excess data members which the server sends to the client are placed into the ExtensionData property of the data contract instance. The functionality can be provided in a reverse manner as well. That is, if the client sends more information to ther service, if the service data contract implements the IExtensibleDataObject interface, then it will have the data encapulsated in a ExtensionData property. The IDesign standard, written by the designers of WCF, states that you should always implement this interface.

INotifyPropertyChanged

When using the Visual Studio generation tool, the data contracts that are created implement the INotifyPropertyChanged interface and have special logic in their set accessors to allow the client to monitor changes to properties. This in no way has any affect on the operations of WCF and are not required. Furthermore, given that the client service contract creation is trivial and that the data contract on the client is the same as the data contract on the server, it's recommended that all proxies be created by hand without use of Visual Studio or svcutil.exe.

Using your iPad for In-Depth Bible Study

Happy iPad Day! Right, right... I know some of you are into those Android or Windows tablet holidays, but still, you just can't beat good ol' iPad day! Today the iPad 2 is being released and I wanted to post my latest [epically long] document to help out fellow Christians utilize their iPad more effectively.

About three years ago I published a document called "Using the iPod Touch (and iPhone) for In-Depth Bible Study". Today, I'm releasing the second edition of that called "Using your iPad for In-Depth Bible Study".

I'd strongly recommend all Christian iPad users to look over the document. Seriously, if you are simply relying on the really, really lame Apple commercials or the App Store for your iPad usage, then, seriously... skim the doc!

You may also want to see Jame White's video on his use of technology at http://www.youtube.com/watch?v=VVodV0WB2qg. He and I independently came to the exactly same usages of technology: Android (with it's free tethering, if you get the right model) is epic for phones, iPad is epic for portable 'puting, and the Kindle is epic for reading (and having it read to you!)

The document is at: http://www.netfxharmonics.com/document/ipad/bible

PowerShell Virtualenv

PowerShell is epic and the ISE is awesome, but for some reason ISE shares variable state between tabs. No idea.

So, if you look at any of the hundreds of utilities I've written, you'll notice that I always wrap my entire PowerShell ps1 in a $virtualenv block:

$virtualenv = {


\#++ content here; don't even bother indenting


}
&$virtualenv

The name comes from the virtualenv Python tool, which makes sure modules are install in a local isolated virtual environment, not on your system as a whole.

Now this script is scoped. All work will be done inside this virtualenv.

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

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....