MSIgnite2016_PSV5Unplugged-Demo.ps1

<#
This scripot is for teaching by having people open it in PowerShell_ISE and selecting a line and
using F8 to run it.
Check out what it does and then explore. Look up the help and try different variations and parameters.
 
As such. This script is NOT meant to be run in it's entirety.
I put a return as the first element to protect against that happening.
#>
 
return


#region JustInCase

<##############################################################################################
The GitHub API throttles the number of API calls from unauthenticated accounts SO if you run into this
you can get around it by providing credentials to the Invoke-RestMethod. The last step of this demo
is to produce a module JPSGIT (available in the gallery) which provides a -Credential parameter but
this is a quick and dirty workaround if you run into in this demo.
 
What id does is remaps the IRM alias to invoke-MYrestmethod which calls invoke-restmethod and supplies
it your github credentials
##############################################################################################>

if (0)
{
    $Cred = Get-Credential -Message "Enter your GitHub Account credentials"

    function Invoke-MyRestMethod
    {
        $pair = "$($cred.UserName):$($cred.GetNetworkCredential().password)"
        $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))

        $basicAuthValue = "Basic $encodedCreds"

        $Headers = @{
            Authorization = $basicAuthValue
        }

        $MoreParams = @{Headers=$Headers}
        Invoke-RestMethod @args @MoreParams
    }
    # Did you ever notice that we don't have a Remove-Alias?
    # We didn't invest in that because we had this mechanism and didn't feel like it was a
    # high volume scenario
    Remove-Item alias:irm -Force
    Set-Alias irm Invoke-MyRestMethod -Force    
}
#endregion

#Region IfUnix
Set-Alias IWR Invoke-WebRequest
Set-Alias IRM Invoke-RestMethod
#endregion

#region Step 1 The Challenge
# Someone said it was easy to use BASH to get info from Github using this:
# curl https://api.github.com/repos/PowerShell/PowerShell/releases| grep download_count releases | awk '{ print $2 }' | sed 's/,//'

# I first tried this using Invoke-WebRequest. I hacked it up to find info.
# NOTE ON UNIX/NanoServer - this first section is not going to work because it utilizes the enhanced parsing capabilities of the Windows
# IE browser. Skip the the IRW (Invoke-WebRequest) section

Gal Curl
$uri = "https://api.github.com/repos/PowerShell/PowerShell/releases"
Invoke-WebRequest $uri
$x = Invoke-WebRequest $uri
$x.ToString() |sls download_count
#NOTE: If you don't already have Show-Object - you can get it from the Public PowerShell gallery
# Install-Module PowerShellCookbook -AllowClobber
Show-Object $x
$x=((iwr $uri).AllElements[3].Innertext|convertfrom-Json)[0].Assets; $x |Sort-Object download_count -d|Ft name,down*;"Total Downloads: $(($x |measure download_count -sum).Sum)"
#############################################################################################
######## LESSON: Hack around and have FUN
#############################################################################################

#The problem was that this was too long so I emailed the team asking for a better approach.
#Lee Holmes pointed out that Invoke-RestMethod was a better cmdlet for REST APIs
$x = irm api.github.com/repos/PowerShell/PowerShell/releases
show-object $x
(irm api.github.com/repos/PowerShell/PowerShell/releases)[0].Assets | Sort-Object -d d* | ft n*,d*
##################################################################################################### LESSON: With a large enough community - there is always someone
######## that knows more about what you want to do than you do
#############################################################################################
#endregion



#region Step 2 Automation
function t 
{
(irm api.github.com/repos/PowerShell/PowerShell/releases)[0].Assets | Sort-Object -d d* | ft n*,d*
}
t
#############################################################################################
######## LESSON: Most Scripts are private and therefore it is fine to break all the rules
######## and be as informal as you like. I have lots of scripts named: a,b,c,d, etc.
#############################################################################################
#endregion



#region Step 3 - EVERYONE IS SAYING X
# At some point there was a comment thread about aliases and an email proported to say that
# "everyone was saying x"
# That didn't sound right so I decided to explore

(irm https://api.github.com/repos/PowerShell/PowerShell-RFC/issues/16/comments?per_page=100).user.login |group |Sort-Object -Descending Count
# Notice that the conversation is dominated by 2 people.
# That made me think that I would like to have a tool to explore GIT COMMENTS

function Get-GitIssueComment
{
    Param
    (
        [Parameter()]
        $Owner = "PowerShell",

        [Parameter()]
        $Repo = "PowerShell-RFC",

        [Parameter(Mandatory=1)]
        [int]
        $Issue
    )

    $uri = "https://api.github.com/repos/$Owner/$Repo/issues/$issue/comments?per_page=100"
    Write-Verbose "URI = $uri"
    Invoke-RestMethod -Uri $uri | Write-Output
}
Get-GitIssueComment -issue 16 -ov c
$c.user.login
(Get-GitIssueComment -issue 16 ).user.login |group -NoElement|Sort-Object count -Descending
cls
Get-GitIssueComment -Issue 16 |ft @{Name="User";expression={$_.user.login}},Body -Wrap
cls
# Someone in the hallway mentioned something about a controversy about a "vulgar" comment
$c.where{$_.body -match "vulgar"} |ft @{Name="User";expression={$_.user.login}},Body -Wrap
# Looks like these two are going at it so let's explore that more
$c.where{$_.user.login -in "dlwyatt","kuldeepdhaka"} |ft @{Name="User";expression={$_.user.login}},Body -Wrap
$c|gm

# Now we use this command on other repos:
Get-GitIssueComment -Owner PowerShell -Repo PowerShell -issue 2233 |
    ft -group @{Label="Day"; exp={([Datetime]$_.Created_at).DayOfWeek}} `
         @{Name="User";expression={$_.user.login}},Body
#############################################################################################
######## LESSON: A little formality makes it a lot more readable.
######## LESSON: Objects ROCK!
#############################################################################################

#endregion



#region Step 4 - Types make things better
# Here I have the same function as before but I add a single step - I add
# a give the generic PSCustomObject a type (just by adding it to PSTYPENAMES)

function Get-GitIssueComment
{
    
    Param
    (
        [Parameter()]
        $Owner = "PowerShell",

        [Parameter()]
        $Repo = "PowerShell-RFC",

        [Parameter(Mandatory=1)]
        [int]
        $Issue
    )

    $uri = "https://api.github.com/repos/$Owner/$Repo/issues/$issue/comments?per_page=100"
    Write-Verbose "URI = $uri"
    $items = Invoke-RestMethod -Uri $uri 
    foreach ($c in $items)
    {
        $c.pstypenames.Insert(0,"JPS.GitIssueComment")
        Write-Output $c
    }
}
# Because it now has a TYPE - I can start partying on that TYPE
Update-TypeData -TypeName JPS.GitIssueComment -MemberType ScriptProperty -MemberName Login -Value {$this.user.login} -force
Update-TypeData -TypeName JPS.GitIssueComment -DefaultDisplayPropertySet id,body -DefaultDisplayProperty id  -DefaultKeyPropertySet id -Force


$c = Get-GitIssueComment -issue 16 
# Notice the new property: Login
$c |gm
cls
$c |Sort-Object Login |ft -GroupBy Login id, body
$C[0] |fl *
# Notice that this gives you just the id and the body & you don't have to use Format-Table
$C[0]
#Tab complete this to understand why
$c[0].PSStandardMembers
# The defaultDisplayProperty is ID
$c |fw -Column 8
# And when you have this stuff, things begin to get fun
$c |group login -NoElement |Sort-Object count -Descending

Get-GitIssueComment -Issue 16 | where{$_.login -in "dlwyatt","kuldeepdhaka"} |ft Login,Body -Wrap
# BUT NO INTELLISENSE - because there is no type defintion for JPS.GITISSUECOMMENT
Get-GitIssueComment -Issue 16 | 

# Notice that even adding: [outputtype("JPS.GitIssueComment")]
# doesn't help (cut and paste that attribute into the above function right before the PARAM statement)


#############################################################################################
######## LESSON: PowerShell has an adaptive type system which you can party on by just adding a string
######## LESSON: TYPES ROCK
#############################################################################################
#endregion



#region Step 5 Lets make a class!

# We couldn't get any intellisense because the system doesn't
# know what the type "NAME" means in terms of properties, methods, etc
# So we want to convert the JSON document we get back into a proper class.

# Notice that the property Definitions have some type information
Get-GitIssueComment -Issue 16 | Get-Member -MemberType Properties

#Here is a function to convert an Object into a Class
function Convert-ObjecttoClass
{
    [CmdletBinding()]
    [OutputType([String])]
    Param
    (
        [Parameter(Mandatory=1)]
        $InputObject,

        [Parameter(Mandatory=1)]
        [String]$ClassName = ""
    )

#This is a HERE STRING. Because it uses " vs ' variables will be expanded
@"
class $ClassName
{
$(
foreach ($p in Get-Member -InputObject $InputObject -MemberType Properties |Sort-Object name)
{
    switch ($p.MemberType)
    {
        "NoteProperty" {
            $type = ($p.Definition -split " ")[0]
            if ($type -in "System.Management.Automation.PSCustomObject")
            {
                "`t[PSCustomObject]`$$($p.Name);`n"
            }else
            {
                "`t[$type]`$$($p.Name);`n"
            }
        }
    }
}
)
}
"@

}

Convert-ObjecttoClass -InputObject (Get-GitIssueComment -Issue 16)[0] -ClassName GitIssueComment

#############################################################################################
######## LESSON: Here documents are great to write scripts which write scripts
#############################################################################################

#endregion



#region Step 6 Now lets use that class!
cls

# 1) Pasted the class
# 2) Added an [OutputType([GitIssueComment])]
# 3) Cast output to the class

class GitIssueComment6
{
    [string]$body;
     [string]$created_at;
     [string]$html_url;
     [int]$id;
     [string]$issue_url;
     [string]$updated_at;
     [string]$url;
     [PSObject]$user;
}
Update-TypeData -TypeName GitIssueComment6 -MemberType ScriptProperty -MemberName Login -Value {$this.user.login} -force
Update-TypeData -TypeName GitIssueComment6 -DefaultDisplayPropertySet id,body -DefaultDisplayProperty id  -DefaultKeyPropertySet id -Force

function Get-GitIssueComment6
{
    [CmdletBinding()]
    [OutputType([GitIssueComment6])]
    Param
    (
        [Parameter()]
        $Owner = "PowerShell",

        [Parameter()]
        $Repo = "PowerShell-RFC",

        [Parameter(Mandatory=1)]
        [int]
        $Issue
    )

    $uri = "https://api.github.com/repos/$Owner/$Repo/issues/$issue/comments?per_page=100"
    Write-Verbose "URI = $uri"
    $items = Invoke-RestMethod -Uri $uri 
    foreach ($c in $items)
    {
        Write-Output ([GitIssueComment6]$c)
    }
}


Get-GitIssueComment6 -Issue 16 | where{$_.login -in "dlwyatt","kuldeepdhaka"} |ft Login,Body -Wrap
#Get-GitIssueComment6 -Issue 16 |

# Did you notice this: [GitIssueComment6]$c
# PowerShell is very accomdating

[GitIssueComment6](@{body="Body"; Created_at="Today";id=3;user="Jeffrey Snover";url="https://microsoft.com"}) |fl *
[GitIssueComment6](@"
body,Created_at,id,user,url
body,Today,13,Jeffrey Snover,https://microsoft.com
"@
 |convertFrom-csv
) | fl *

#############################################################################################
######## LESSON: Having a class and an OUTPUT type gives you intellisense
######## LESSON: PowerShell will convert lots of things into classes instances
#############################################################################################

#endregion



#region Step 7 Now let's improve that Class
# Changes:
# - Capitalized Property names to make it cleaner
# - Added types to properties
# - Added a couple of helper methods

class GitIssueComment7
{
    [string]$Body;
     [DateTime]$Created_at;
     [URI]$Html_url;
     [int]$Id;
     [URI]$Issue_url;
     [DateTime]$Updated_at;
     [URI]$Url;
     [PSObject]$User;
    [Timespan]Get_Freshness() {return [DateTIme]::Now - $this.updated_at}
    [void]Show_Comment(){
        if (gcm xdg-open)
        {
            xdg-open $this.Html_url
        }elseif (gcm start-process)
        {
            Start-process $this.Html_url
        }
     }
}
function Get-GitIssueComment7
{
    [OutputType([GitIssueComment7])]
    Param
    (
        [Parameter()]
        $Owner = "PowerShell",

        [Parameter()]
        $Repo = "PowerShell-RFC",

        [Parameter(Mandatory=1)]
        [int]
        $Issue
    )

    $uri = "https://api.github.com/repos/$Owner/$Repo/issues/$issue/comments?per_page=100"
    Write-Verbose "URI = $uri"
    $items = Invoke-RestMethod -Uri $uri 
    foreach ($c in $items)
    {
        Write-Output ([GitIssueComment7]$c)
    }
}
Update-TypeData -TypeName GitIssueComment7 -MemberType ScriptProperty -MemberName Login -Value {$this.user.login} -force
Update-TypeData -TypeName GitIssueComment7 -DefaultDisplayPropertySet id,body -DefaultDisplayProperty Title  -DefaultKeyPropertySet id -Force
Update-TypeData -TypeName GitIssueComment7 -MemberType ScriptProperty -MemberName Freshness -Value {$this.Get_freshness()} -Force
Update-TypeData -TypeName GitIssueComment7 -MemberType ScriptProperty -MemberName DayCreated -Value {$this.Created_at.Dayofweek} -Force

Get-GitIssueComment7 -issue 16 -ov c |Sort-Object created_at |ft -GroupBy DayCreated Id,Freshness,Body
$c[0] |fl *
$c |gm
$c[0].Issue_url
$c[0].Created_at |fl *
$c[0].Created_at |gm
$c[0].Show_Comment()

#############################################################################################
######## LESSON: You can incrementally add more formalism with types and unlock a lot of extra features
#############################################################################################

#endregion