Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to become a SHiPS wright - Building with SHiPS

Glenn
April 27, 2019

How to become a SHiPS wright - Building with SHiPS

A Shipwright an artisan skilled in one or more of the tasks required to build vessels. A SHiPSwright is an artisan skilled in one or more of the tasks required to build PowerShell Providers. The SHiPS toolkit has been around for a while but it can be a little difficult to get started.

Links
https://github.com/PowerShell/SHiPS

Glenn

April 27, 2019
Tweet

More Decks by Glenn

Other Decks in Technology

Transcript

  1. @glennsarti SHiPS is a PowerShell provider. More accurately it is

    a provider platform that simplifies developing PowerShell providers. - https://www.powershellgallery.com/packages/SHiPS
  2. @glennsarti PowerShell providers provide access to data and components that

    would not otherwise be easily accessible at the command line. The data is presented in a consistent format that resembles a file system drive.. - about_providers
  3. @glennsarti PS C:\Users\glenn.sarti> Get-PSProvider Name Capabilities Drives ---- ------------ ------

    Registry ShouldProcess {HKLM, HKCU} Alias ShouldProcess {Alias} Environment ShouldProcess {Env} FileSystem Filter, ShouldProcess, Credentials {C} Function ShouldProcess {Function} Variable ShouldProcess {Variable}
  4. @glennsarti PS C:\Users\glenn.sarti> Get-ChildItem HKLM:\System Hive: HKEY_LOCAL_MACHINE\System Name Property ----

    -------- ActivationBroker ControlSet001 DriverDatabase Version : 167772160 SchemaVersion : 65536 Architecture : 9 UpdateDate : {64, 148, 108, 19...} OemInfMap : {255, 255, 255, 255...} HardwareConfig LastConfig : {a4c71b4c-22d3-11b2-a85c-f593fd660ebe} LastId : 0 …
  5. @glennsarti SHiPS is a PowerShell provider. More accurately it is

    a provider platform that simplifies developing PowerShell providers. - https://www.powershellgallery.com/packages/SHiPS
  6. @glennsarti 2018 2019 2014 2015 2016 2017 Simplex P2F Azure

    Cloud Shell Public Preview SHiPS on PS Gallery Releases (0.8.1) SHiPS
  7. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root class Parent : SHiPSDirectory

    { Parent([string]$name): base($name) { } [object[]] GetChildItem() { return [Child]::new("Child"); } } class Child : SHiPSLeaf { Child($name): base($name) { } }
  8. @glennsarti Deepak - https://github.com/DexterPOSH Glenn - https://github.com/glennsarti Ravi - https://github.com/rchaganti

    Patrick - https://github.com/SeeminglyScience PS - https://github.com/PowerShell
  9. @glennsarti @{ RootModule = 'PSSummitNA2019.psm1' ModuleVersion = '1.0' GUID =

    '2e946999-7d90-4bfb-88f1-9336e3a01587' Author = 'glenn.sarti' CompanyName = 'Glenn Sarti' Description = 'A PowerShell Provider for the Pow…' # Modules that must be imported into the global … RequiredModules = @('SHiPS') }
  10. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } }
  11. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } }
  12. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } }
  13. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } }
  14. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } }
  15. @glennsarti using namespace Microsoft.PowerShell.SHiPS # Root [SHiPSProvider(UseCache=$true)] class Summit2019 :

    SHiPSDirectory { Summit2019([string]$name): base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } } ???
  16. @glennsarti PS > Import-Module .\PSSummitNA2019.psd1 PS > New-PSDrive -Name Summit2019

    ` -PSProvider SHiPS ` -Root 'PSSummitNA2019#Summit2019' Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- Summit2019 SHiPS PSSum…
  17. @glennsarti PS > Import-Module .\PSSummitNA2019.psd1 PS > New-PSDrive -Name Summit2019

    ` -PSProvider SHiPS ` -Root 'PSSummitNA2019#Summit2019' Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- Summit2019 SHiPS PSSum… PS > gci Summit2019:\ PS >
  18. @glennsarti # Directory Nodes # Speakers [SHiPSProvider(UseCache=$true)] class Speakers :

    SHiPSDirectory { Speakers([string]$name): base($name) { } [object[]] GetChildItem() { return @() } } # Agenda [SHiPSProvider(UseCache=$true)]
  19. @glennsarti } } # Agenda [SHiPSProvider(UseCache=$true)] class Agenda : SHiPSDirectory

    { Agenda([string]$name): base($name) { } [object[]] GetChildItem() { return @() } }
  20. @glennsarti # Root [SHiPSProvider(UseCache=$true)] class Summit2019 : SHiPSDirectory { Summit2019([string]$name):

    base($name) { } [object[]] GetChildItem() { $obj = @() return $obj; } } # Directory Nodes # Speakers
  21. @glennsarti # Root [SHiPSProvider(UseCache=$true)] class Summit2019 : SHiPSDirectory { Summit2019([string]$name):

    base($name) { } [object[]] GetChildItem() { $obj = @() $obj += [Speakers]::new('Speakers’); $obj += [Agenda]::new('Agenda’); return $obj; } }
  22. @glennsarti PS > Import-Module .\PSSummitNA2019.psd1 PS > New-PSDrive -Name Summit2019

    ` -PSProvider SHiPS ` -Root 'PSSummitNA2019#Summit2019' Name Used (GB) Free (GB) Provider Root ---- --------- --------- -------- ---- Summit2019 SHiPS PSSum…
  23. @glennsarti # Private Functions $Script:DataSource = Join-Path -Path $PSScriptRoot -ChildPath

    'Data' Function Get-SpeakersObject { if ($Script:SpeakerObject -eq $null) { $Script:SpeakerObject = Get-Content … | ConvertFrom-Json } Write-Output $Script:SpeakerObject } Function Remove-HTML($RawString) { $result = $RawString $result = $result -replace '<[^>]+>',‘’ Write-Output $result }
  24. @glennsarti # Leaf Nodes # A Speaker [SHiPSProvider(UseCache=$true)] class Speaker

    : SHiPSLeaf { [String]$Name; [String]$FirstName; [String]$LastName; [String]$Bio; Speaker ([string]$name, [Object]$data): base($name) { $this.PopulateFromData($data) } [void] PopulateFromData([Object]$data) { $this.Name = $data.name # VERY basic name splitting $NameArray = $this.Name -split " ", 2 $this.FirstName = $NameArray[0] $this.LastName = $NameArray[1] $this.Bio = Remove-HTML -RawString $data.overview } }
  25. @glennsarti # Leaf Nodes # A Speaker [SHiPSProvider(UseCache=$true)] class Speaker

    : SHiPSLeaf { [String]$Name; [String]$FirstName; [String]$LastName; [String]$Bio; Speaker ([string]$name, [Object]$data): base($name) { $this.PopulateFromData($data) } [void] PopulateFromData([Object]$data) { $this.Name = $data.name # VERY basic name splitting $NameArray = $this.Name -split " ", 2 $this.FirstName = $NameArray[0] $this.LastName = $NameArray[1] $this.Bio = Remove-HTML -RawString $data.overview } }
  26. @glennsarti # Leaf Nodes # A Speaker [SHiPSProvider(UseCache=$true)] class Speaker

    : SHiPSLeaf { [String]$Name; [String]$FirstName; [String]$LastName; [String]$Bio; Speaker ([string]$name, [Object]$data): base($name) { $this.PopulateFromData($data) } [void] PopulateFromData([Object]$data) { $this.Name = $data.name # VERY basic name splitting $NameArray = $this.Name -split " ", 2 $this.FirstName = $NameArray[0] $this.LastName = $NameArray[1] $this.Bio = Remove-HTML -RawString $data.overview } }
  27. @glennsarti # Leaf Nodes # A Speaker [SHiPSProvider(UseCache=$true)] class Speaker

    : SHiPSLeaf { [String]$Name; [String]$FirstName; [String]$LastName; [String]$Bio; Speaker ([string]$name, [Object]$data): base($name) { $this.PopulateFromData($data) } [void] PopulateFromData([Object]$data) { $this.Name = $data.name # VERY basic name splitting $NameArray = $this.Name -split " ", 2 $this.FirstName = $NameArray[0] $this.LastName = $NameArray[1] $this.Bio = Remove-HTML -RawString $data.overview } }
  28. @glennsarti # Speakers [SHiPSProvider(UseCache=$true)] class Speakers : SHiPSDirectory { Speakers([string]$name):

    base($name) { } [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null) (Get-SpeakersObject).items | ForEach-Object -Process { $obj.Add([Speaker]::new($_.name, $_)) } return $obj; }
  29. @glennsarti PS > gci Summit2019:\Speakers | Select-Object -First 5 Directory:

    Summit2019:\Speakers Mode Name ---- ---- . James O'Neill . Christoph Bergmeister . Walter Legowski . Paul DeArment Jr . Daniel Bohannon PS >
  30. @glennsarti PS > gci Summit2019:\Speakers | select -First 1 –Property

    * SSItemMode : . PSPath : SHiPS\SHiPS::PSSummitNA2019#Summit2019\Speakers\James PSParentPath : SHiPS\SHiPS::PSSummitNA2019#Summit2019\Speakers PSChildName : James O'Neill PSDrive : Summit2019 PSProvider : SHiPS\SHiPS PSIsContainer : False Name : James O'Neill FirstName : James LastName : O'Neill Bio : James O'Neill spent the first 10 years of this century talking about it to anyone who will listen ever since. and DSC to automate processes for one of Britiain's be … PS >
  31. @glennsarti # A Session on the agenda [SHiPSProvider(UseCache=$true)] class AgendaSession

    : SHiPSLeaf { [String]$Id; [String]$Name; [datetime]$Start; [datetime]$End; [String]$Location; [String]$Description; Hidden [Object] $Data AgendaSession([string]$id, $data): base($id) { $this.Id = $Id $this.Data = $data
  32. @glennsarti # A Session on the agenda [SHiPSProvider(UseCache=$true)] class AgendaSession

    : SHiPSLeaf { [String]$Id; [String]$Name; [datetime]$Start; [datetime]$End; [String]$Location; [String]$Description; Hidden [Object] $Data AgendaSession([string]$id, $data): base($id) { $this.Id = $Id $this.Data = $data
  33. @glennsarti [String]$Description; Hidden [Object] $Data AgendaSession([string]$id, $data): base($id) { $this.Id

    = $Id $this.Data = $data $this.Name = $data.name $this.Description = Remove-HTML $data.overview $this.Start = ConvertFrom-EpochTime -Value $data.start_time $this.End = ConvertFrom-EpochTime -Value $data.end_time $this.Location = ($data.regions | Select-Object -First 1 | ForEach-Object { $_.name }) } }
  34. @glennsarti # AgendaTrackSummary [SHiPSProvider(UseCache=$true)] class AgendaTrackSummary : SHiPSDirectory { [String]$Name;

    [Int]$Sessions; Hidden [Hashtable]$Filter; AgendaTrackSummary([string]$name, [Hashtable]$filter): base($name) { $this.Name = $name $this.Filter = $filter $this.Sessions = (Get-Sessions -Filter $this.Filter | Measure-Object).Count; } [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null)
  35. @glennsarti # AgendaTrackSummary [SHiPSProvider(UseCache=$true)] class AgendaTrackSummary : SHiPSDirectory { [String]$Name;

    [Int]$Sessions; Hidden [Hashtable]$Filter; AgendaTrackSummary([string]$name, [Hashtable]$filter): base($name) { $this.Name = $name $this.Filter = $filter $this.Sessions = (Get-Sessions -Filter $this.Filter | Measure-Object).Count; } [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null)
  36. @glennsarti } [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null) Get-Sessions

    -Filter $this.Filter | ForEach-Object -Process { $obj.Add([AgendaSession]::new($_.Id, $_)) } return $obj; } }
  37. @glennsarti [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null) $obj.Add([AgendaTrackSummary]::new('All', @{

    })) $obj.Add([AgendaTrackSummary]::new('Day 1 - Mon', @{ 'Day' = 29 })) $obj.Add([AgendaTrackSummary]::new('Day 2 - Tue', @{ 'Day' = 30 })) $obj.Add([AgendaTrackSummary]::new('Day 3 - Wed', @{ 'Day' = 1 })) $obj.Add([AgendaTrackSummary]::new('Day 4 - Thu', @{ 'Day' = 2 })) $trackList = @{} (Get-SessionsObject).sessions | ForEach-Object -Process { $_.tracks | ForEach-Object -Process { $trackList[$_.name] = $true } } $trackList.GetEnumerator() | ForEach-Object -Process { $obj.Add([AgendaTrackSummary]::new($_.Key, @{ 'Track' = $_.Key })) } return $obj; }
  38. @glennsarti [object[]] GetChildItem() { $obj = New-Object System.Collections.ArrayList($null) $obj.Add([AgendaTrackSummary]::new('All', @{

    })) $obj.Add([AgendaTrackSummary]::new('Day 1 - Mon', @{ 'Day' = 29 })) $obj.Add([AgendaTrackSummary]::new('Day 2 - Tue', @{ 'Day' = 30 })) $obj.Add([AgendaTrackSummary]::new('Day 3 - Wed', @{ 'Day' = 1 })) $obj.Add([AgendaTrackSummary]::new('Day 4 - Thu', @{ 'Day' = 2 })) $trackList = @{} (Get-SessionsObject).sessions | ForEach-Object -Process { $_.tracks | ForEach-Object -Process { $trackList[$_.name] = $true } } $trackList.GetEnumerator() | ForEach-Object -Process { $obj.Add([AgendaTrackSummary]::new($_.Key, @{ 'Track' = $_.Key })) } return $obj; }
  39. @glennsarti Name Sessions ---- -------- All 80 Day 1 -

    Mon 9 Day 2 - Tue 25 Day 3 - Wed 26 Day 4 - Thu 20 In the Cloud 8 Meal 9 Vendor 2 General 12 Bits and Bytes 14 Automate All the Things 17 OnRamp 3 PowerShell Language 17
  40. @glennsarti SSItemMode : . PSPath : SHiPS\SHiPS::PSSummitNA2019#Summit2019\Agenda\Day 2 - Tu

    PSParentPath : SHiPS\SHiPS::PSSummitNA2019#Summit2019\Agenda\Day 2 - Tu PSChildName : 61513 PSDrive : Summit2019 PSProvider : SHiPS\SHiPS PSIsContainer : False Id : 61513 Name : Breakfast Start : 30/04/2019 08:00:00 End : 30/04/2019 09:00:00 Location : Center Hall A Description : Join us for a hot breakfast. We'll have announcements SSItemMode : . PSPath : SHiPS\SHiPS::PSSummitNA2019#Summit2019\Agenda\Day 2 - Tu
  41. @glennsarti # VERY basic name splitting $NameArray = $this.Name -split

    " ", 2 $this.FirstName = $NameArray[0] $this.LastName = $NameArray[1] $this.Bio = Remove-HTML -RawString $data.overview } [string] GetContent() { return "# " + $this.Name + "`n`n## Bio`n`n" + $this.Bio } }
  42. @glennsarti $this.Name = $data.name $this.Description = Remove-HTML $data.overview $this.Start =

    ConvertFrom-EpochTime -Value $data.start_time $this.End = ConvertFrom-EpochTime -Value $data.end_time $this.Location = ($data.regions | Select-Object -First 1 | ForEach- } [string] GetContent() { return "### " + $this.name + "`n`n" + ` "Time:" + $this.Start.ToString('hh:mm tt') + ` " Location:" + $this.Location + "`n`n" + ` $this.Description + "`n`n" } }
  43. @glennsarti PS > Get-Content 'Summit2019:\Speakers\Glenn Sarti' # Glenn Sarti ##

    Bio I’m located in Perth (Western Australia) where I now work at Puppet as Senior Software Developer on the Windows team. Fro … PS >
  44. @glennsarti PS > Get-Content 'Summit2019:\Agenda\Day 2 - Tue\61451' ### Parselmouth

    - bending the PowerShell language Time:11:00 AM Location:406 I want to introduce the audience to the high-level design of the tokenizer, parser and compiler in PowerShell Core, and then take it a step … PS >
  45. @glennsarti class Speaker : SHiPSLeaf { … [String[]]$SessionIDs; [String[]]$Sessions; [int]$NumSessions;

    [String[]]$SessionTimes; [void] PopulateFromData([Object]$data) { … $this.Sessions = @() $this.SessionIDs = @() $this.SessionTimes = @() Get-Sessions -Filter @{ 'Speaker' = $data.Id} | ForEach-Object { $this.Sessions += $_.name $start = (ConvertFrom-EpochTime -Value $_.start_time).ToString("d MMM, hh:mm tt") $this.SessionIDs += $_.id $this.SessionTimes += $start } $this.NumSessions = $this.Sessions.Count } …
  46. @glennsarti class Speaker : SHiPSLeaf { … [String[]]$SessionIDs; [String[]]$Sessions; [int]$NumSessions;

    [String[]]$SessionTimes; [void] PopulateFromData([Object]$data) { … $this.Sessions = @() $this.SessionIDs = @() $this.SessionTimes = @() Get-Sessions -Filter @{ 'Speaker' = $data.Id} | ForEach-Object { $this.Sessions += $_.name $start = (ConvertFrom-EpochTime -Value $_.start_time).ToString("d MMM, hh:mm tt") $this.SessionIDs += $_.id $this.SessionTimes += $start } $this.NumSessions = $this.Sessions.Count } …
  47. @glennsarti class AgendaSession : SHiPSLeaf { … [String[]]$Speakers AgendaSession([string]$id, $data):

    base($id) { … $this.Speakers = $this.Data.item_ids | ForEach-Object -Process { $SpeakerID = $_ Write-Output (Get-SpeakersObject).items | ` Where-Object { $_.id -eq $SpeakerId } | ` ForEach-Object { Write-Output $_.name } } } …
  48. @glennsarti class AgendaSession : SHiPSLeaf { … [String[]]$Speakers AgendaSession([string]$id, $data):

    base($id) { … $this.Speakers = $this.Data.item_ids | ForEach-Object -Process { $SpeakerID = $_ Write-Output (Get-SpeakersObject).items | ` Where-Object { $_.id -eq $SpeakerId } | ` ForEach-Object { Write-Output $_.name } } } …
  49. @glennsarti PS > gci 'Summit2019:\Speakers\Glenn Sarti' | Select * ...

    Name : Glenn Sarti FirstName : Glenn LastName : Sarti Bio : I’m located in Perth (Western Australia)… SessionIDs : {61496, 61497} Sessions : {Beyond Pester 102: Acceptance testing w… NumSessions : 2 SessionTimes : {30 Apr, 02:00 PM, 1 May, 11:00 AM}
  50. @glennsarti PS > gci 'Summit2019:\Agenda\Day 2 - Tue\61451' | Select

    * ... Id : 61451 Name : Parselmouth - bending the PowerShell lang… Start : 30/04/2019 11:00:00 End : 30/04/2019 11:45:00 Location : 406 Description : I want to introduce the audience to the … Speakers : Mathias Jessen
  51. @glennsarti @{ RootModule = 'PSSummitNA2019.psm1’ ModuleVersion = '1.0’ GUID =

    '2e946999-7d90-4bfb-88f1-9336e3a01587’ Author = 'glenn.sarti’ CompanyName = 'Glenn Sarti’ Description = 'A PowerShell Provider for the Pow…’ # Modules that must be imported into the global … RequiredModules = @('SHiPS’) FormatsToProcess = @('PSSummitNA2019.Format.ps1xml') }
  52. @glennsarti PS > gci Summit2019:\Speakers | ` Select-Object –First 3

    | Format-Table Mode Name ---- ---- . James O'Neill . Christoph Bergmeister . Walter Legowski
  53. @glennsarti Name Session Times Bio ---- ------------- --- James O'Neill

    {2 May, 01:00 PM} James O'Neill spe… Christoph Bergmeister {30 Apr, 02:00 PM} I am a developer … Walter Legowski {1 May, 11:00 AM} ###**Walter Legow…
  54. @glennsarti PS > gci 'Summit2019:\Agenda\In the Cloud' | ` Sort-Object

    –Property Start | ` Select-Object –First 3 | Format-List Mode : . Name : 61464 Mode : . Name : 61501 Mode : . Name : 61466
  55. @glennsarti Id : 61464 Name : Introduction to Serverless Functions

    Start : 30/04/2019 09:00:00 Location : 405 Speakers : Kirk Munro Description : I've spent a lot of time working with server … Id : 61501 Name : Demystifying Microsoft's Cloud Automation products Start : 30/04/2019 11:00:00 Location : 405 Speakers : Jaap Brasser Description : Azure offers an evergrowing suite of produ … Id : 61466 Name : It’s PowerShell In the Cloud – Welcome to Azure Cloud Shell Start : 30/04/2019 13:00:00 Location : 405 Speakers : Michael Bender Description : As more organizations move towards the cloud and using Microsoft …
  56. @glennsarti • Start with some planning • Create the root

    object first • … then the Directories • … then the Leaves • Expand – Test – Iterate • Then make it pretty
  57. @glennsarti PS> New-PSDrive -Name Github -PSProvider SHiPS ` -root 'Github#Root’

    ` -Credential (Get-Credential 'glennsarti') PowerShell credential request Enter your credentials. Password for user glennsarti: ********* The provider does not support the use of credentials. Perform the operation At line:1 char:1 + New-PSDrive -Name Github -PSProvider SHiPS -root 'Github#Root' -Crede ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotImplemented: (:) [], PSNotSupportedException + FullyQualifiedErrorId : NotSupported PS > ☹
  58. @glennsarti … for better navigation user experience … an author

    can decide to set UseCache to true, to cache data returned from Get-ChildItem to memory in the current PowerShell session. - SHiPS documentation [SHiPSProvider(UseCache=$true)] class Speaker : SHiPSLeaf {
  59. @glennsarti PS > gci Summit2019:\Speakers –Filter Glenn* PS > gci

    Summit2019:\Speakers | ` Where-Object { $_.Name –like 'Glenn*'} Filtering
  60. @glennsarti PS > gci Summit2019:\Agenda\All | ` Where-Object { $_.Track

    –eq 'General' } PS > gci Summit2019:\Agenda –Track General PS > gci Summit2019:\Agenda –Day 2 PS > gci Summit2019:\Agenda –Speaker 'Glenn Sarti' … or combine them -Track General –Day 2 Dynamic Parameters
  61. @glennsarti Doesn’t support credentials Tiny supported cmdlet list Doesn’t support

    New, Remove, Rename, Move, Copy, *ItemProperty No Drive information within Provider Context
  62. THANK YOU! Please use the event app or Socio to

    submit a session rating! @glennsarti http://glennsarti.github.io/ https://speakerdeck.com/glennsarti https://github.com/glennsarti/PSSummitNA2019-Ships
  63. @glennsarti Resources Ravikanth Chaganti PS Conf EU 2018 - SHiPS:

    Walk-through a bare-metal system configuration https://github.com/psconfeu/2018/tree/master/Ravikanth%20Chaganti/SHiPS SHiPS GH Repo https://github.com/PowerShell/SHiPS SHiPS PS Gallery https://www.powershellgallery.com/packages/SHiPS https://www.powershellgallery.com/packages?q=ships about_format.ps1xml https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/ about_format.ps1xml?view=powershell-5.1
  64. @glennsarti Resources Writing a PowerShell Formatting File https://docs.microsoft.com/en-us/powershell/developer/format/ writing-a-powershell-formatting-file SHiPS

    Default formatting https://github.com/PowerShell/SHiPS/blob/master/src/Modules/SHiPS.formats.ps1xml Images https://unsplash.com