SpiceWorld2016_PowerShellUnplugged.ps1
#region Step 1 The Challenge # someone said it was easy to use BASH to get info from Github # 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 .\show-object1 $x $j = ((iwr $uri).AllElements[3].Innertext|convertfrom-Json) .\show-object1 $j $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 .\show-object1 (irm api.github.com/repos/PowerShell/PowerShell/releases) (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 ############################### return #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 informal ############################### #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=300 -ov a).user.login |group -ov b|sort -Descending Count -ov c .\Show-Object1.ps1 $a[0].user $a[0] | % {$_.user} |sort -Unique login | %{ add-member -in $_ -MemberType NoteProperty -name followCount -Value $((irm $_.followers_url).count) -pass -force } | %{ $user = (irm $_.url); add-member -in $_ -MemberType NoteProperty -name Name -Value $user.Name -force; add-member -in $_ -MemberType NoteProperty -name Bio -Value $User.bio -pass -force } | sort followcount,login |ft login,name,followcount,bio (irm https://api.github.com/repos/PowerShell/PowerShell-RFC/issues/16/comments?per_page=300 -ov a).user.login |group -ov b|sort -Descending Count -ov c # 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 ( # Param1 help description [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 |out-tab $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 ( # Param1 help description [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 |out-tab $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 gm -i $c[0].PSStandardMembers $c[0].PSStandardMembers.DefaultDisplayPropertySet $c[0].PSStandardMembers.DefaultDisplayProperty # The defaultDisplayProperty is ID $c |fw -AutoSize # 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 ############################### ######## 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 $c | Get-Member -MemberType Properties #Here is a function to convert an Object into a Class function Convert-ObjecttoClass { [CmdletBinding()] Param ( # Param1 help description [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([GitIssueComment6])] # 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 ( # Param1 help description [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 | where # Did you notice this: [GitIssueComment6]$c # PowerShell is very accomdating [GitIssueComment6](@{body="Body"; Created_at="Today";id=3;user="Jeffrey";url="https://microsoft.com"}) |fl * [GitIssueComment6](@" body,Created_at,id,user,url body,Today,13,Jeffrey,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(){&"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" $this.Html_url} } function Get-GitIssueComment7 { [OutputType([GitIssueComment7])] Param ( # Param1 help description [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 #Regin Just in case $Mycred = Get-Credential jsnover15@gmail.com function Invoke-MyRestMethod { [CmdletBinding()] Param ( [Parameter(Position=0)] $uri ) $MoreParams = @{} $pair = "$($mycred.UserName):$($mycred.GetNetworkCredential().password)" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = "Basic $encodedCreds" $Headers = @{ Authorization = $basicAuthValue } $MoreParams = @{Headers=$Headers} Invoke-RestMethod @PSBoundParameters @MoreParams } Remove-Item alias:irm -Force sal irm Invoke-MyRestMethod function out-tab { [CmdletBinding()] [Alias()] [OutputType([int])] Param ( # Param1 help description [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)] $InputObject ) begin { $t = $psise.CurrentPowerShellTab.Files.Add() $items = @() } process { $items += $InputObject; } end { $t.Editor.Text = "<#`n$($items|out-string)`n#>" } } $data = Import-Clixml C:\demo\Comments.xml Invoke-FakeRM { return $data } #region |