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 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 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 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 -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 -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 -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 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 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 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()] [Output([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 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([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) { 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(){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 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 |