├── ClassBasics.ps1 ├── Creating Class-Based PowerShell tools.pdf ├── Demo1.ps1 ├── Demo2.ps1 ├── Demo3.ps1 ├── Demo4.ps1 ├── Demo5.ps1 ├── DemoOrder.ps1 ├── MyFileObject ├── MyFileObject.psm1 └── Tests │ └── MyFileObject.Tests.ps1 ├── README.md ├── Samples ├── DataReport419.docx ├── DataReport634.docx ├── DataReport800.docx ├── Demo-Function1.ps1 ├── Demo-Function2.ps1 ├── Demo-Function3.ps1 ├── Demo-Help.ps1 ├── Demo-PSDrive.ps1 ├── Demo-PipeObjects.ps1 ├── Demo-Remoting.ps1 ├── Demo-SaveRemoteHelp.ps1 ├── Demo-Script1.ps1 ├── Demo-Script2.ps1 ├── Demo-WMI.ps1 ├── Exec107.docx ├── Exec312.docx ├── Exec338.docx ├── Exec443.docx ├── Exec612.docx ├── InvoiceReview143.pdf ├── bandquiz-1.png ├── bieber.mp4 ├── bieber.zip ├── cimscriptmaker-1.png ├── cimscriptmaker-2.png ├── cimscriptmaker-3.png ├── cimscriptmaker-4.png ├── cimscriptmaker-5.png ├── cimscriptmaker-6.png ├── cimscriptmaker-7.png ├── cimscriptmaker-8.png ├── demo-legacyremoting.ps1 ├── demo-updatehelp.txt ├── files.xml ├── foo.dat ├── foo2.dat ├── install.exe ├── install2.exe ├── jlo.mp4 ├── jlo.zip ├── listen.mp3 ├── listen.zip ├── num.dat ├── readme.txt ├── something.dat ├── test1.bmp └── test2.bmp ├── a-note-on-functions.md ├── file.txt ├── file.zip ├── legacy.ps1 └── license.txt /ClassBasics.ps1: -------------------------------------------------------------------------------- 1 | 2 | return "This is a walkthrough demo file, not a script." 3 | 4 | Class MyStarShip { 5 | #properties 6 | [string]$Name 7 | [int]$Crew 8 | [datetime]$ManufactureDate 9 | [int]$QuantumTorpedoCount 10 | 11 | #methods 12 | #must define what they write to the pipeline, if anything 13 | [void]FireTorpedo() { 14 | Write-Host "Fire!" -ForegroundColor Red 15 | $this.QuantumTorpedoCount-- 16 | } 17 | [void]FireTorpedo([int]$Count) { 18 | Write-Host "Fire $Count!!" -ForegroundColor Red 19 | $this.QuantumTorpedoCount-=$Count 20 | } 21 | [timespan]GetAge() { 22 | $age = (Get-Date).AddYears(123) - $this.ManufactureDate 23 | #methods must use RETURN is writing something to the pipeline 24 | return $age 25 | } 26 | 27 | #constructors 28 | myStarShip([string]$Name,[int]$CrewSize) { 29 | $this.name = $Name 30 | $this.Crew = $CrewSize 31 | $this.QuantumTorpedoCount = 50 32 | $this.ManufactureDate = (Get-Date).AddYears(120).AddMonths(4).AddDays(3) 33 | } 34 | } 35 | 36 | [MyStarShip]::new 37 | [MyStarShip]::new("Proxima",20) 38 | 39 | $ship = new-object -TypeName MyStarShip -ArgumentList "USS Snover",100 40 | $ship 41 | $ship | get-member 42 | 43 | $ship.GetAge() 44 | $ship.GetAge().ToString() 45 | 46 | $ship 47 | $ship.FireTorpedo() 48 | $ship.FireTorpedo(3) 49 | $ship.QuantumTorpedoCount 50 | 51 | #have more fun with starships at https://github.com/jdhitsolutions/myStarShip -------------------------------------------------------------------------------- /Creating Class-Based PowerShell tools.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Creating Class-Based PowerShell tools.pdf -------------------------------------------------------------------------------- /Demo1.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | 4 | #define some properties 5 | 6 | Class MyFileObject { 7 | 8 | [ValidateNotNullorEmpty()] 9 | [string]$Path 10 | [string]$Name 11 | [string]$Extension 12 | [string]$Directory 13 | [int]$Size 14 | [datetime]$Created 15 | [datetime]$Modified 16 | 17 | } 18 | 19 | #load the class into your session 20 | Return 21 | 22 | #WALKTHROUGH 23 | 24 | #different ways to create a new instance of the object 25 | New-Object MyFileObject 26 | 27 | [myfileobject]::new() 28 | 29 | [myfileobject]::new() | get-member 30 | 31 | -------------------------------------------------------------------------------- /Demo2.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | 4 | #define some properties 5 | #add a constructor or two 6 | 7 | Class MyFileObject { 8 | 9 | #region Properties 10 | 11 | [ValidateNotNullorEmpty()] 12 | [string]$Path 13 | [string]$Name 14 | [string]$Extension 15 | [string]$Directory 16 | [int]$Size 17 | [datetime]$Created 18 | [datetime]$Modified 19 | 20 | #endregion 21 | 22 | 23 | #region Constructors 24 | 25 | MyFileObject([string]$FilePath) { 26 | If (Test-Path -Path $Filepath) { 27 | $item = Get-Item -Path $Filepath 28 | $this.path = $item.fullname 29 | $this.Name = $item.Name 30 | $this.Extension = $item.Extension.Substring(1) 31 | $this.size = $item.Length 32 | $this.Created = $item.CreationTime 33 | $this.Modified = $item.LastWriteTime 34 | $this.Directory = $item.Directory 35 | } 36 | else { 37 | Write-Warning "Failed to find $filepath" 38 | #don't create the object 39 | Break 40 | } 41 | 42 | } 43 | 44 | #endregion 45 | } 46 | 47 | Return 48 | 49 | #walkthrough demo 50 | cls 51 | 52 | [myfileobject]::new.overloadDefinitions 53 | 54 | $f = New-Object MyFileObject -ArgumentList .\Demo1.ps1 55 | $f 56 | $f | get-member 57 | 58 | #test bad path 59 | [MyFileObject]::New("x:\foo.txt") 60 | 61 | #creating several objects 62 | dir .\ -File | foreach { New-Object myfileobject $_.FullName} -------------------------------------------------------------------------------- /Demo3.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | 4 | #define some properties 5 | #add a constructor or two 6 | #add some methods 7 | 8 | Class MyFileObject { 9 | 10 | #region Properties 11 | 12 | [ValidateNotNullorEmpty()] 13 | [string]$Path 14 | [string]$Name 15 | [string]$Extension 16 | [string]$Directory 17 | [int]$Size 18 | [datetime]$Created 19 | [datetime]$Modified 20 | 21 | #endregion 22 | 23 | #region Methods 24 | 25 | [timespan]GetCreatedAge() { 26 | $result = (Get-Date) - $this.Created 27 | Return $result 28 | } 29 | 30 | [timespan]GetModifiedAge() { 31 | $result = (Get-Date) - $this.Modified 32 | Return $result 33 | } 34 | 35 | [void]Refresh() { 36 | If (Test-Path -Path $this.path) { 37 | $item = Get-Item -Path $this.path 38 | $this.size = $item.Length 39 | $this.Modified = $item.LastWriteTime 40 | } 41 | else { 42 | Write-Warning "Failed to find $($this.path). Cannot refresh the object." 43 | } 44 | } 45 | 46 | #endregion 47 | 48 | #region Constructors 49 | 50 | MyFileObject([string]$FilePath) { 51 | 52 | If (Test-Path -Path $Filepath) { 53 | $item = Get-Item -Path $Filepath 54 | $this.path = $item.fullname 55 | $this.Name = $item.Name 56 | $this.Extension = $item.Extension.Substring(1) 57 | $this.size = $item.Length 58 | $this.Created = $item.CreationTime 59 | $this.Modified = $item.LastWriteTime 60 | $this.Directory = $item.Directory 61 | } 62 | else { 63 | Write-Warning "Failed to find $filepath" 64 | #don't create the object 65 | Break 66 | } 67 | 68 | } 69 | 70 | #endregion 71 | 72 | } 73 | 74 | cls 75 | Return 76 | 77 | #demo this version 78 | 79 | $f = New-Object MyFileObject -ArgumentList .\file.txt 80 | $f 81 | $f | get-member 82 | $f.GetCreatedAge() 83 | $f.GetModifiedAge() 84 | $f.GetModifiedAge().ToString() 85 | 86 | # modify file.txt 87 | add-content -Value "this is something else" -Path .\file.txt 88 | 89 | $f.Refresh() 90 | $f 91 | $f.GetModifiedAge().ToString() 92 | -------------------------------------------------------------------------------- /Demo4.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | 4 | #define some properties 5 | #add a constructor or two 6 | #add some methods 7 | #enhancements and an enumeration 8 | 9 | 10 | #added an enumeration 11 | enum FileClass { 12 | File 13 | Script 14 | Text 15 | Office 16 | Graphic 17 | Executable 18 | System 19 | Media 20 | } 21 | 22 | #a helper function 23 | Function Get-FileClass { 24 | [cmdletbinding()] 25 | Param([string]$Extension) 26 | 27 | Switch -Regex ($Extension) { 28 | 29 | "bat|ps1|psm1|psd1|ps1xml|vbs|wpf" { [fileclass]::Script } 30 | "txt|log|xml" { [fileclass]::Text } 31 | "do[ct](x)?|xls(x)?|p[po]t(x)?|pdf" { [fileclass]::Office } 32 | "exe|cmd|application" { [fileclass]::Executable } 33 | "sys|dll" { [fileclass]::System } 34 | "bmp|jpg|png|tif|gif|jpeg" { [fileclass]::Graphic } 35 | "mp3|wav|mp4|avi|wmf" { [fileClass]::Media } 36 | Default { [fileclass]::File } 37 | } 38 | 39 | } 40 | 41 | Class MyFileObject { 42 | 43 | #region Properties 44 | [ValidateNotNullorEmpty()] 45 | [string]$Path 46 | [string]$Name 47 | [string]$Extension 48 | [string]$Directory 49 | [int]$Size 50 | [datetime]$Created 51 | [datetime]$Modified 52 | [fileclass]$FileClass #<----- Added 53 | hidden[string]$Owner #<----- Added 54 | hidden[string]$Basename #<----- Added 55 | #endregion 56 | 57 | #region Methods 58 | [timespan]GetCreatedAge() { 59 | $result = (Get-Date) - $this.Created 60 | Return $result 61 | } 62 | 63 | [timespan]GetModifiedAge() { 64 | $result = (Get-Date) - $this.Modified 65 | Return $result 66 | } 67 | 68 | [void]Refresh() { 69 | If (Test-Path -Path $this.path) { 70 | $item = Get-Item -Path $this.path 71 | $this.size = $item.Length 72 | $this.Modified = $item.LastWriteTime 73 | } 74 | else { 75 | Write-Warning "Failed to find $($this.path). Cannot refresh the object." 76 | } 77 | } 78 | 79 | #vvvvvvvvvvvvvvvvvvvvvvvvvv---NEW---vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 80 | [string]GetFileType() { 81 | $result = cmd /c assoc ".$($this.Extension)" 82 | if ($result -match "=") { 83 | Return $result.split("=")[1] 84 | } 85 | else { 86 | return $result 87 | } 88 | } 89 | 90 | [void]Zip() { 91 | $destination = Join-Path -Path $this.Directory -ChildPath "$($this.basename).zip" 92 | Compress-Archive -Path $this.Path -DestinationPath $destination 93 | } 94 | 95 | [void]Zip([string]$DestinationFolder) { 96 | $destination = Join-Path -Path $DestinationFolder -ChildPath "$($this.basename).zip" 97 | Compress-Archive -Path $this.Path -DestinationPath $destination 98 | 99 | } 100 | 101 | #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 102 | #endregion 103 | 104 | #region Constructors 105 | MyFileObject([string]$FilePath) { 106 | 107 | If (Test-Path -Path $Filepath) { 108 | $item = Get-Item -Path $Filepath 109 | $this.path = $item.fullname 110 | $this.Name = $item.Name 111 | $this.Extension = $item.Extension.Substring(1) 112 | $this.size = $item.Length 113 | $this.Created = $item.CreationTime 114 | $this.Modified = $item.LastWriteTime 115 | $this.Directory = $item.Directory 116 | $this.owner = ($item | Get-ACL).owner 117 | $this.Basename = $item.BaseName 118 | $this.FileClass = Get-FileClass -Extension $item.Extension 119 | } 120 | else { 121 | Write-Warning "Failed to find $filepath" 122 | #don't create the object 123 | Break 124 | } 125 | } 126 | #endregion 127 | 128 | } 129 | 130 | cls 131 | Return 132 | 133 | #demo this version 134 | 135 | $f = New-Object MyFileObject -ArgumentList .\file.txt 136 | $f | get-member 137 | 138 | #can use it if you know it on hidden properties 139 | $f.owner 140 | 141 | #display it 142 | $f | get-member -Force -MemberType Properties 143 | 144 | #try the new methods 145 | $f.GetFileType() 146 | 147 | $f.zip() 148 | dir *.zip 149 | 150 | #zip to a folder 151 | $f.zip("c:\") 152 | dir C:\*.zip 153 | 154 | -------------------------------------------------------------------------------- /Demo5.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | 4 | #define some properties 5 | #add a constructor or two 6 | #add some methods 7 | #enhancements and an enumeration 8 | #added tooling around the class and moved methods to external functions 9 | #hide the Zip method 10 | 11 | #added an enumeration 12 | enum FileClass { 13 | Script 14 | Text 15 | Office 16 | Graphic 17 | Executable 18 | System 19 | Media 20 | File 21 | } 22 | 23 | #a helper function 24 | Function Get-FileClass { 25 | [cmdletbinding()] 26 | Param([string]$Extension) 27 | 28 | Switch -Regex ($Extension) { 29 | 30 | "bat|ps1|psm1|psd1|ps1xml|vbs|wpf" { [fileclass]::Script } 31 | "txt|log|xml" { [fileclass]::Text } 32 | "do[ct](x)?|xls(x)?|p[po]t(x)?|pdf" { [fileclass]::Office } 33 | "exe|cmd|application" { [fileclass]::Executable } 34 | "sys|dll" { [fileclass]::System } 35 | "bmp|jpg|png|tif|gif|jpeg" { [fileclass]::Graphic } 36 | "mp3|wav|mp4|avi|wmf" {[FileClass]::Media} 37 | Default { [fileclass]::File } 38 | } 39 | } 40 | 41 | #these functions won't be exposed so I can use non-standard names 42 | Function ZipFile { 43 | [cmdletbinding()] 44 | Param( 45 | [string]$Path, 46 | [string]$Destination 47 | ) 48 | 49 | Try { 50 | Compress-Archive -Path $Path -DestinationPath $Destination -CompressionLevel Optimal -ErrorAction Stop 51 | } 52 | Catch { 53 | Throw $_ 54 | } 55 | } 56 | 57 | Function Get-FType { 58 | [cmdletbinding()] 59 | Param([string]$Extension) 60 | 61 | #supress errors for the CMD expression 62 | $ErrorActionPreference = "SilentlyContinue" 63 | $result = cmd /c assoc ".$($Extension)" 64 | if ($result -match "=") { 65 | $result.split("=")[1] 66 | } 67 | else { 68 | "Unassociated" 69 | } 70 | } 71 | 72 | Class MyFileObject { 73 | 74 | #region Properties 75 | [ValidateNotNullorEmpty()] 76 | [string]$Path 77 | [string]$Name 78 | [string]$Extension 79 | [string]$Directory 80 | [int]$Size 81 | [datetime]$Created 82 | [datetime]$Modified 83 | [fileclass]$FileClass 84 | hidden[string]$Owner 85 | hidden[string]$Basename 86 | 87 | #endregion 88 | 89 | #region Methods 90 | #these were simple enough to leave internal to the class 91 | [timespan]GetCreatedAge() { 92 | $result = (Get-Date) - $this.Created 93 | Return $result 94 | } 95 | 96 | [timespan]GetModifiedAge() { 97 | $result = (Get-Date) - $this.Modified 98 | Return $result 99 | } 100 | 101 | [void]Refresh() { 102 | If (Test-Path -Path $this.path) { 103 | $item = Get-Item -Path $this.path 104 | $this.size = $item.Length 105 | $this.Modified = $item.LastWriteTime 106 | } 107 | else { 108 | Write-Warning "Failed to find $($this.path). Cannot refresh the object." 109 | } 110 | } 111 | 112 | #These were moved to external functions 113 | [string]GetFileType() { 114 | $r = Get-Ftype -extension $this.Extension 115 | return $r 116 | } 117 | 118 | #these methods are hidden 119 | hidden[void]Zip() { 120 | $destination = Join-Path -Path $this.Directory -ChildPath "$($this.basename).zip" 121 | ZipFile -Path $this.Path -Destination $destination 122 | } 123 | 124 | hidden[void]Zip([string]$DestinationFolder) { 125 | $destination = Join-Path -Path $DestinationFolder -ChildPath "$($this.basename).zip" 126 | ZipFile -Path $this.Path -Destination $destination 127 | 128 | } 129 | 130 | #endregion 131 | 132 | #region Constructors 133 | MyFileObject([string]$FilePath) { 134 | 135 | If (Test-Path -Path $Filepath) { 136 | $item = Get-Item -Path $Filepath 137 | $this.path = $item.fullname 138 | $this.Name = $item.Name 139 | $this.Extension = $item.Extension.Substring(1) 140 | $this.size = $item.Length 141 | $this.Created = $item.CreationTime 142 | $this.Modified = $item.LastWriteTime 143 | $this.Directory = $item.Directory 144 | $this.owner = ($item | Get-ACL).owner 145 | $this.Basename = $item.BaseName 146 | $this.FileClass = Get-FileClass -Extension $item.Extension 147 | } 148 | else { 149 | Write-Warning "Failed to find $filepath" 150 | #don't create the object 151 | Break 152 | } 153 | 154 | } 155 | #endregion 156 | } 157 | 158 | cls 159 | Return 160 | 161 | #Walkthrough 162 | 163 | $f = New-Object MyFileObject -ArgumentList .\file.txt 164 | $f | Get-Member 165 | $f.zip() 166 | $f.GetFileType() 167 | 168 | #use functions 169 | Get-fileclass $f 170 | 171 | 172 | -------------------------------------------------------------------------------- /DemoOrder.ps1: -------------------------------------------------------------------------------- 1 | #it is assumed you are running this file in the PowerShell ISE 2 | 3 | return "This is a walk-through demo file not a script to run." 4 | 5 | #change folder to demo root 6 | # set-location 'C:\scripts\Demo Class-Based Tools' 7 | 8 | cls 9 | psedit .\legacy.ps1 10 | psedit .\classbasics.ps1 11 | psedit .\Demo1.ps1 12 | psedit .\Demo2.ps1 13 | psedit .\Demo3.ps1 14 | psedit .\Demo4.ps1 15 | psedit .\Demo5.ps1 16 | psedit .\MyFileObject\MyFileObject.psm1 17 | 18 | #region demo the module 19 | 20 | import-module .\MyFileObject\MyFileObject.psm1 -force 21 | get-command -module MyFileObject 22 | 23 | help New-MyFileObject 24 | $f = New-MyFileObject .\Samples\bieber.mp4 25 | $f 26 | $f | gm 27 | $f | Compress-MyFileObject -WhatIf 28 | 29 | #do an entire folder 30 | $all = dir .\Samples -file | New-MyFileObject 31 | $all | Out-GridView -Title Samples 32 | $all | group fileclass 33 | $all.where({$_.fileclass -eq 'media'}).foreach({Compress-MyFileObject -FileObject $_ -Passthru}) 34 | 35 | #what are some other ways of getting the same result from this code 36 | $all | Select-Object *,@{Name="AgeCategory";Expression={ 37 | Switch ($_.GetModifiedAge().TotalDays) { 38 | {$_ -gt 365} {'1YrPlus' ; Break} 39 | {$_ -gt 180 -AND $_ -le 365} {'1Yr' ; Break} 40 | {$_ -gt 90 -AND $_ -le 180} {'6Mo' ; Break} 41 | {$_ -gt 30 -AND $_ -le 90} {'3Mo' ; Break} 42 | {$_ -gt 7 -AND $_ -le 30} { '1Mo'; Break } 43 | Default {"New"} 44 | } 45 | }} | Group AgeCategory 46 | 47 | #endregion -------------------------------------------------------------------------------- /MyFileObject/MyFileObject.psm1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | #Previous steps 4 | #define some properties 5 | #add a constructor or two 6 | #add some methods 7 | #enhancements and an enumeration 8 | #added tooling around the class and moved methods to external functions 9 | #created a module 10 | 11 | #DEMO THIS IN A NEW CONSOLE SESSION 12 | 13 | #added an enumeration 14 | enum FileClass { 15 | File 16 | Script 17 | Text 18 | Office 19 | Graphic 20 | Executable 21 | System 22 | Media 23 | } 24 | 25 | Class MyFileObject { 26 | 27 | #region Properties 28 | 29 | [ValidateNotNullorEmpty()] 30 | [string]$Path 31 | [string]$Name 32 | [string]$Extension 33 | [string]$Directory 34 | [int]$Size 35 | [datetime]$Created 36 | [datetime]$Modified 37 | [fileclass]$FileClass 38 | hidden[string]$Owner 39 | hidden[string]$Basename 40 | hidden[string]$Computername = $env:Computername 41 | 42 | #endregion 43 | 44 | #region Methods 45 | 46 | [timespan]GetCreatedAge() { 47 | $result = (Get-Date) - $this.Created 48 | Return $result 49 | } 50 | 51 | [timespan]GetModifiedAge() { 52 | $result = (Get-Date) - $this.Modified 53 | Return $result 54 | } 55 | 56 | [void]Refresh() { 57 | If (Test-Path -Path $this.path) { 58 | $item = Get-Item -Path $this.path 59 | $this.size = $item.Length 60 | $this.Modified = $item.LastWriteTime 61 | } 62 | else { 63 | Write-Warning "Failed to find $($this.path). Cannot refresh the object." 64 | } 65 | } 66 | 67 | [string]GetFileType() { 68 | #call the private helper function 69 | $r = GetFtype -extension $this.Extension 70 | return $r 71 | } 72 | 73 | <# 74 | these hidden methods use the private helper function. I 75 | will use public functions to expose them in a controlled manner. 76 | #> 77 | hidden[void]Zip() { 78 | $destination = Join-Path -Path $this.Directory -ChildPath "$($this.basename).zip" 79 | ZipFile -Path $this.Path -Destination $destination 80 | } 81 | 82 | hidden[void]Zip([string]$DestinationFolder) { 83 | $destination = Join-Path -Path $DestinationFolder -ChildPath "$($this.basename).zip" 84 | ZipFile -Path $this.Path -Destination $destination 85 | 86 | } 87 | 88 | #endregion 89 | 90 | #region Constructors 91 | MyFileObject([string]$FilePath) { 92 | 93 | If (Test-Path -Path $Filepath) { 94 | write-Verbose "Getting file information from $filepath" 95 | $item = Get-Item -Path $Filepath 96 | $this.path = $item.fullname 97 | $this.Name = $item.Name 98 | #added code to handle files without extensions 99 | $this.Extension = If ($item.Extension) {$item.Extension.Substring(1)} else {$Null} 100 | $this.size = $item.Length 101 | $this.Created = $item.CreationTime 102 | $this.Modified = $item.LastWriteTime 103 | $this.Directory = $item.Directory 104 | Write-Verbose "Getting owner from the ACL" 105 | $this.owner = ($item | Get-ACL).owner 106 | $this.Basename = $item.BaseName 107 | #call a private function 108 | Write-Verbose "Getting file class information" 109 | $this.FileClass = GetFileClass -Extension $item.Extension 110 | } 111 | else { 112 | Write-Warning "Failed to find $filepath." 113 | #don't create the object 114 | Break 115 | } 116 | 117 | } 118 | 119 | #endregion 120 | 121 | } #end class definition 122 | 123 | #region private helper functions 124 | #these functions won't be exposed so I can use non-standard names 125 | 126 | Function GetFileClass { 127 | [cmdletbinding()] 128 | Param([string]$Extension) 129 | 130 | Switch -Regex ($Extension) { 131 | "bat|ps1|psm1|psd1|ps1xml|vbs|wpf" { [fileclass]::Script } 132 | "txt|log|xml" { [fileclass]::Text } 133 | "do[ct](x)?|xls(x)?|p[po]t(x)?|pdf" { [fileclass]::Office } 134 | "exe|cmd|application" { [fileclass]::Executable } 135 | "sys|dll" { [fileclass]::System } 136 | "bmp|jpg|png|tif|gif|jpeg" { [fileclass]::Graphic } 137 | "mp3|wav|mp4|avi|wmf" {[FileClass]::Media} 138 | Default {[FileClass]::File} 139 | } 140 | } 141 | 142 | Function ZipFile { 143 | [cmdletbinding(SupportsShouldProcess)] 144 | Param( 145 | [string]$Path, 146 | [string]$Destination 147 | ) 148 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 149 | 150 | $params = @{ 151 | Path = $Path 152 | DestinationPath = $Destination 153 | CompressionLevel = "Optimal" 154 | ErrorAction = "Stop" 155 | } 156 | 157 | if ($WhatIfPreference) { 158 | $params.Add("WhatIf", $True) 159 | } 160 | Try { 161 | Compress-Archive @params 162 | } 163 | Catch { 164 | Throw $_ 165 | } 166 | Write-Verbose "Ending $($MyInvocation.MyCommand)" 167 | 168 | } 169 | 170 | Function GetFType { 171 | [cmdletbinding()] 172 | Param([string]$Extension) 173 | 174 | #supress errors for the CMD expression 175 | $ErrorActionPreference = "SilentlyContinue" 176 | $result = cmd /c assoc ".$($Extension)" 177 | if ($result -match "=") { 178 | $result.split("=")[1] 179 | } 180 | else { 181 | "Unassociated" 182 | } 183 | } 184 | 185 | #endregion 186 | 187 | #region Public functions for my class that will be exported in the module 188 | 189 | Function New-MyFileObject { 190 | [cmdletbinding()] 191 | Param( 192 | [Parameter( 193 | Mandatory, 194 | HelpMessage = "Enter the path to a file", 195 | ValueFromPipeline, 196 | ValueFromPipelineByPropertyName 197 | )] 198 | [Alias("fullname")] 199 | [ValidateNotNullorEmpty()] 200 | [string[]]$Path 201 | 202 | ) 203 | 204 | Begin { 205 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 206 | } #begin 207 | Process { 208 | foreach ($item in $Path) { 209 | if (Test-Path -path $item) { 210 | Write-Verbose "Creating MyFileObject for $item" 211 | Try { 212 | New-Object MyFileObject $item -ErrorAction Stop 213 | } 214 | Catch { 215 | Write-Warning "Error creating object for $item. $($_.exception.message)" 216 | } 217 | } 218 | else { 219 | Throw $_ 220 | } 221 | } 222 | } #process 223 | 224 | End { 225 | Write-Verbose "Ending $($MyInvocation.MyCommand).name" 226 | 227 | } #end 228 | } 229 | 230 | Function Update-MyFileObject { 231 | [cmdletbinding()] 232 | Param( 233 | [Parameter( 234 | Mandatory, 235 | ValueFromPipeline 236 | )] 237 | [ValidateNotNullOrEmpty()] 238 | [MyFileObject[]]$FileObject 239 | ) 240 | 241 | Begin { 242 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 243 | } #begin 244 | Process { 245 | foreach ($item in $FileObject) { 246 | Write-Verbose "Refreshing MyFileObject $($item.name)" 247 | $item.refresh() 248 | 249 | } 250 | } #process 251 | 252 | End { 253 | Write-Verbose "Ending $($MyInvocation.MyCommand)" 254 | 255 | } #end 256 | } 257 | 258 | Function Compress-MyFileObject { 259 | [cmdletbinding(SupportsShouldProcess)] 260 | Param( 261 | [Parameter( 262 | Mandatory, 263 | ValueFromPipeline 264 | )] 265 | [ValidateNotNullOrEmpty()] 266 | [MyFileObject[]]$FileObject, 267 | [string]$DestinationPath, 268 | [switch]$Passthru 269 | ) 270 | 271 | Begin { 272 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 273 | } #begin 274 | Process { 275 | foreach ($item in $FileObject) { 276 | Write-Verbose "Processing $($item.path)" 277 | if (-Not $DestinationPath) { 278 | $DestinationPath = $item.Directory 279 | } 280 | 281 | Write-Verbose "Testing destination path: $DestinationPath" 282 | If (Test-Path -Path $DestinationPath) { 283 | Write-Verbose "Zipping MyFileObject $($item.name) to $DestinationPath" 284 | 285 | $Destination = Join-path -Path $DestinationPath -ChildPath "$($item.basename).zip" 286 | $zipParams = @{ 287 | Path = $item.Path 288 | Destination = $Destination 289 | } 290 | 291 | #pass these on to the internal function 292 | if ($WhatIfPreference) { 293 | $zipParams.Add("WhatIf", $True) 294 | } 295 | 296 | if ($VerbosePreference -eq "continue") { 297 | $zipParams.Add("Verbose", $True) 298 | } 299 | 300 | #Call the internal function directly 301 | Write-Verbose "Invoking ZipFile()" 302 | ZipFile @zipParams 303 | 304 | if ($passthru -AND (-NOT $WhatIfPreference)) { 305 | Get-Item -Path $destination 306 | 307 | } 308 | } #if Test-Path $DestinationPath 309 | else { 310 | Throw "Exception for $($MyInvocation.MyCommand) : Can't find $DestinationPath" 311 | } 312 | } #foreach 313 | } #process 314 | 315 | End { 316 | Write-Verbose "Ending $($MyInvocation.MyCommand)" 317 | 318 | } #end 319 | } 320 | 321 | Function Get-MyFileObject { 322 | [cmdletbinding(DefaultParameterSetName = "path")] 323 | Param( 324 | [Parameter(Position = 0, Mandatory, ParameterSetName = "path")] 325 | [ValidateScript( {Test-Path $_})] 326 | [string]$Path, 327 | [Parameter(ParameterSetName = "path")] 328 | [Switch]$Recurse, 329 | 330 | [Parameter(Position = 1, Mandatory, ParameterSetName = "file")] 331 | [ValidateNotNullOrEmpty()] 332 | [string]$FileName 333 | ) 334 | 335 | Begin { 336 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 337 | 338 | } #begin 339 | 340 | Process { 341 | If ($PSCmdlet.ParameterSetName -eq 'path') { 342 | Write-Verbose "Getting files from $Path" 343 | Get-Childitem @PSBoundParameters -file | New-MyFileObject 344 | } 345 | else { 346 | Write-Verbose "Getting file $filepath" 347 | Get-ChildItem -path $filename | New-MyFileObject 348 | } 349 | } 350 | 351 | End { 352 | Write-Verbose "Ending $($MyInvocation.MyCommand)" 353 | 354 | } #end 355 | 356 | } 357 | 358 | Function Get-MyFileObjectAge { 359 | [cmdletbinding()] 360 | Param( 361 | [Parameter(Position = 0, Mandatory, ValueFromPipeline)] 362 | [ValidateNotNullOrEmpty()] 363 | [myFileObject]$MyFileObject 364 | ) 365 | 366 | Begin { 367 | Write-Verbose "Starting $($MyInvocation.MyCommand)" 368 | 369 | } #begin 370 | 371 | Process { 372 | Write-Verbose "Processing $($MyFileObject.name)" 373 | $myFileObject | Select Path, Name, Size, Created, 374 | @{Name = "CreatedAge"; Expression = {$_.getcreatedAge()}}, 375 | Modified, 376 | @{Name = "ModifiedAge"; Expression = {$_.getmodifiedAge()}} 377 | 378 | } 379 | 380 | End { 381 | Write-Verbose "Ending $($MyInvocation.MyCommand)" 382 | 383 | } #end 384 | 385 | 386 | } 387 | 388 | #endregion 389 | 390 | Export-ModuleMember -Function Get-MyFileObjectAge, Get-MyFileObject, 391 | New-MyFileObject, Update-MyFileObject, Compress-MyFileObject 392 | 393 | <# 394 | Next steps: 395 | Add custom format extensions 396 | Add custom type extensions 397 | help documentation 398 | add a manifest - nothing to declare for the class 399 | Separate functions from class definition into different files 400 | #> 401 | -------------------------------------------------------------------------------- /MyFileObject/Tests/MyFileObject.Tests.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/MyFileObject/Tests/MyFileObject.Tests.ps1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo-Class-Based-Tools 2 | 3 | * These files are intended to be run interactively to demonstrate how you might create a class-based PowerShell tool. 4 | * The demo files are meant to show a progression using simple classes. At the end of the process is a basic module built around a class. 5 | * The demo files are written assuming you are using the PowerShell ISE. 6 | * The module is not intended for production use as currently written and is not necessarily a complete example of best scripting practices. 7 | * The demonstrations require a Windows platform running PowerShell 5.0 or later. They have **not** been tested with PowerShell 6.0. 8 | 9 | These example files have *nothing* to do with creating a DSC class-based resource. 10 | 11 | To run the demos, download the files to a new folder. Change location to that folder then open DemoOrder.ps1 in the PowerShell ISE, making sure that the ISE is also set to location with these files. 12 | 13 | ## Suggested Best Practices 14 | * Define classes and enumerations separately from code that uses them. 15 | * Create functions around your class to make it easier to use. 16 | * Package your class and supporting files as a module. 17 | * Consider adding type and format extensions. 18 | 19 | _Last updated: 26 September 2017_ 20 | -------------------------------------------------------------------------------- /Samples/DataReport419.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/DataReport419.docx -------------------------------------------------------------------------------- /Samples/DataReport634.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/DataReport634.docx -------------------------------------------------------------------------------- /Samples/DataReport800.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/DataReport800.docx -------------------------------------------------------------------------------- /Samples/Demo-Function1.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | #this is a simple function 4 | 5 | Function Get-MyComputer { 6 | 7 | Param( 8 | [string[]]$computername=$env:COMPUTERNAME 9 | ) 10 | 11 | Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername | 12 | Select-Object -property @{Name="Computername";Expression={$_.CSName}}, 13 | @{Name="OS";Expression={$_.Caption}}, 14 | @{Name="SvcPack";Expression={$_.CSDVersion}}, 15 | @{Name="LastBoot";Expression={ 16 | $_.ConvertToDateTime($_.LastBootUpTime)}}, 17 | @{Name="Uptime";Expression={ 18 | (Get-Date) - $_.ConvertToDateTime($_.LastBootUpTime)}}, 19 | @{Name="%Free";Expression={ 20 | $C = Get-WmiObject -Class win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $_.CSName 21 | [math]::Round(($C.freespace/$c.size)*100,2) 22 | }} 23 | 24 | } #end function 25 | 26 | #end of script -------------------------------------------------------------------------------- /Samples/Demo-Function2.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | #this is a more advanced function 4 | 5 | Function Get-MyComputer { 6 | 7 | [cmdletbinding()] 8 | 9 | Param( 10 | [Parameter(Mandatory, 11 | HelpMessage="Enter computer name")] 12 | [ValidatePattern("^chi-")] 13 | [string[]]$Computername 14 | ) 15 | 16 | Write-Verbose "Getting my computer information." 17 | 18 | Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername | 19 | Select-Object -property @{ 20 | Name="Computername";Expression={$_.CSName}}, 21 | @{Name="OS";Expression={$_.Caption}}, 22 | @{Name="SvcPack";Expression={$_.CSDVersion}}, 23 | @{Name="LastBoot";Expression={ 24 | $_.ConvertToDateTime($_.LastBootUpTime)}}, 25 | @{Name="Uptime";Expression={ 26 | (Get-Date) - $_.ConvertToDateTime($_.LastBootUpTime)}}, 27 | @{Name="%Free";Expression={ 28 | $C = Get-WmiObject -Class win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $_.CSName 29 | [math]::Round(($C.freespace/$c.size)*100,2) 30 | }} 31 | 32 | } #end function 33 | -------------------------------------------------------------------------------- /Samples/Demo-Function3.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | #this is a complete advanced function 4 | 5 | Function Get-MyComputer { 6 | 7 | <# 8 | .Synopsis 9 | Get corporate server information. 10 | .Description 11 | This command uses WMI to retrieve basic server information. 12 | .Example 13 | PS C:\> get-mycomputer chi-dc01 14 | .Example 15 | PS C:\> get-content c:\work\servers.txt | get-mycomputer | export-csv c:\work\chidata.csv 16 | 17 | #> 18 | 19 | [cmdletbinding()] 20 | 21 | Param( 22 | [Parameter(Mandatory=$true, 23 | HelpMessage="Enter computer name", 24 | ValueFromPipeline=$true)] 25 | #[ValidatePattern("^chi-")] 26 | [string[]]$Computername 27 | ) 28 | 29 | Begin { 30 | Write-Verbose "Getting my computer information" 31 | } 32 | 33 | Process { 34 | foreach ($computer in $computername) { 35 | Write-Verbose "Processing $($Computer.toUpper())" 36 | Try { 37 | #get Operating system information 38 | $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop 39 | 40 | #if no error get logical disk information 41 | if ($os) { 42 | Write-Verbose "...querying Drive C:" 43 | $C = Get-WmiObject -Class win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $OS.CSName 44 | } #if $os 45 | #create a custom object 46 | $LastBoot = $OS.ConvertToDateTime($OS.LastBootUpTime) 47 | #format last boot time 48 | $myObj = [ordered]@{ 49 | Computername = $OS.CSName 50 | OS= $OS.Caption 51 | SvcPack=$OS.CSDVersion 52 | LastBoot= $LastBoot 53 | Uptime= (Get-Date) - $LastBoot 54 | '%Free'= [math]::Round(($C.freespace/$c.size)*100,2) 55 | } 56 | #write the custom object to the pipeline 57 | New-Object -TypeName PSObject -Property $myObj 58 | } #Try 59 | Catch { 60 | Write-Warning "Failed to get computer information from $($computer.toUpper())" 61 | } #Catch 62 | } #foreach computer 63 | } #Process 64 | 65 | End { 66 | Write-Verbose "Ending computer information function" 67 | } #end 68 | 69 | } #end function -------------------------------------------------------------------------------- /Samples/Demo-Help.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Demo-Help.ps1 -------------------------------------------------------------------------------- /Samples/Demo-PSDrive.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Demo-PSDrive.ps1 -------------------------------------------------------------------------------- /Samples/Demo-PipeObjects.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Demo-PipeObjects.ps1 -------------------------------------------------------------------------------- /Samples/Demo-Remoting.ps1: -------------------------------------------------------------------------------- 1 | #demo remoting 2 | 3 | #run these interactively 4 | # enable-psremoting 5 | # test-wsman chi-core01 6 | # enter-pssession chi-core01 7 | 8 | help invoke-command -ShowWindow 9 | 10 | invoke-command -scriptblock { dir c:\ -Hidden} -computer chi-core01,chi-fp02 11 | 12 | help New-PSSession 13 | 14 | $computers = "chi-core01","chi-dc01","chi-dc02","chi-dc04","chi-hvr2" 15 | $sessions = new-pssession $computers 16 | $sessions 17 | 18 | Get-PSSession 19 | #could do this: 20 | get-process lsass -ComputerName $computers 21 | #this scales better and is faster 22 | invoke-command {get-process lsass } -Session $sessions 23 | 24 | #sometimes things only work locally in PS v4 25 | help Get-Process 26 | get-process -IncludeUserName 27 | #this will fail 28 | get-process -ComputerName chi-hvr2 -includeusername 29 | 30 | #run it remotely 31 | invoke-command {get-process -includeusername} -ComputerName chi-hvr2 32 | 33 | #or run scripts 34 | #This version will hide the computername and 35 | #runspaceId 36 | invoke-command -FilePath S:\Get-DriveUtilization.ps1 -session $sessions -HideComputerName | 37 | Select * -excludeproperty RunspaceID | 38 | Out-Gridview -Title "Drive Report" 39 | 40 | #sessions will remain until you close PowerShell. Or manually remove them. 41 | Get-PSSession | Remove-PSSession 42 | 43 | #demo remoting in the ISE 44 | 45 | help psremoting 46 | help about_remote* 47 | 48 | cls 49 | -------------------------------------------------------------------------------- /Samples/Demo-SaveRemoteHelp.ps1: -------------------------------------------------------------------------------- 1 |  2 | #get local installed modules that have update help 3 | $modules = get-module -ListAvailable | where {$_.HelpInfoUri} 4 | 5 | #get remote modules not found locally 6 | $modules+= get-module -list -CimSession chi-core01 | where {$_.name -notin $modules.name -AND $_.HelpInfoUri} 7 | 8 | $modules.count 9 | 10 | $modules | Save-Help -DestinationPath c:\work\help -Force 11 | 12 | #later 13 | get-module -ListAvailable | Update-Help -SourcePath C:\work\help -Force 14 | 15 | -------------------------------------------------------------------------------- /Samples/Demo-Script1.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | <# 4 | This is a simple demonstration script. 5 | The main par is a one-line command you could 6 | have run interactively in the console. 7 | 8 | Using a script saves typing and provides 9 | consistency. 10 | #> 11 | 12 | #a collection of computers to query 13 | $computername="chi-dc01","chi-dc02","chi-dc04" 14 | 15 | #Use WMI to display some customized OS information 16 | Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername | 17 | Select-Object -Property @{Name="Computername"; 18 | Expression={$_.CSName}}, 19 | @{Name="OS";Expression={$_.Caption}}, 20 | @{Name="SvcPack";Expression={$_.CSDVersion}}, 21 | @{Name="LastBoot"; 22 | Expression={$_.ConvertToDateTime($_.LastBootUpTime)}}, 23 | @{Name="Uptime";Expression={ 24 | (Get-Date) - $_.ConvertToDateTime($_.LastBootUpTime)}}, 25 | @{Name="%Free";Expression={ 26 | $C = Get-WmiObject -Class win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $_.CSName 27 | [math]::Round(($C.freespace/$c.size)*100,2) 28 | }} -------------------------------------------------------------------------------- /Samples/Demo-Script2.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | <# 4 | this is a simple demonstration script but with 5 | a bit more flexibility. 6 | #> 7 | 8 | Param( 9 | [string[]]$computername=@("chi-dc01","chi-dc02","chi-dc04") 10 | ) 11 | 12 | Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername | 13 | Select-Object -property @{Name="Computername"; 14 | Expression={$_.CSName}}, 15 | @{Name="OS";Expression={$_.Caption}}, 16 | @{Name="SvcPack";Expression={$_.CSDVersion}}, 17 | @{Name="LastBoot";Expression={ 18 | $_.ConvertToDateTime($_.LastBootUpTime)}}, 19 | @{Name="Uptime";Expression={ 20 | (Get-Date) - $_.ConvertToDateTime($_.LastBootUpTime)}}, 21 | @{Name="%Free";Expression={ 22 | $C = Get-WmiObject -Class win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $_.CSName 23 | [math]::Round(($C.freespace/$c.size)*100,2) 24 | }} 25 | 26 | #end of script -------------------------------------------------------------------------------- /Samples/Demo-WMI.ps1: -------------------------------------------------------------------------------- 1 | #demo WMI 2 | help Get-WmiObject 3 | 4 | get-wmiobject -list -class win32* 5 | 6 | get-wmiobject -ClassName win32_operatingsystem 7 | get-wmiobject win32_operatingsystem | select * 8 | 9 | $computers = "chi-fp02","chi-core01", 10 | "chi-dc01","chi-dc02","chi-dc04","chi-hvr2" 11 | 12 | get-wmiobject win32_operatingsystem -computername $computers | 13 | select PSComputername,Caption,OSArchitecture,ServicePackMajorVersion,InstallDate 14 | 15 | #WMI Dates can be converted 16 | 17 | get-wmiobject win32_operatingsystem -computername $computers | 18 | select PSComputername,Caption,OSArchitecture, 19 | ServicePackMajorVersion, 20 | @{Name="Installed"; 21 | Expression={$_.ConvertToDatetime($_.InstallDate)}} 22 | 23 | #filtering 24 | #gwmi is an alias for Get-WMIObject 25 | gwmi win32_logicaldisk -ComputerName chi-fp02 26 | 27 | #several options: 28 | gwmi -query "Select * from win32_logicaldisk where drivetype=3" -ComputerName chi-fp02 29 | gwmi win32_logicaldisk -filter "drivetype=3" -ComputerName chi-fp02 30 | 31 | #NOT THIS 32 | gwmi win32_logicaldisk -ComputerName chi-fp02 | 33 | where { $_.drivetype -eq 3} 34 | 35 | #also supports credentials 36 | gwmi win32_logicaldisk -filter "deviceid='c:'" -ComputerName $computers -credential "globomantics\administrator" | 37 | Select PSComputername,Caption,Size,Freespace 38 | 39 | #customize the output 40 | gwmi win32_logicaldisk -filter "deviceid='c:'" -ComputerName $computers -credential "globomantics\administrator" | 41 | Select PSComputername,Caption,@{Name="SizeGB"; 42 | Expression={($_.Size/1gb) -as [int]}}, 43 | @{Name="FreeGB";Expression={$_.Freespace/1gb}}, 44 | @{Name="PctFree"; 45 | Expression={ ($_.freespace/$_.size)*100}} 46 | 47 | help wmi 48 | 49 | cls -------------------------------------------------------------------------------- /Samples/Exec107.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Exec107.docx -------------------------------------------------------------------------------- /Samples/Exec312.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Exec312.docx -------------------------------------------------------------------------------- /Samples/Exec338.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Exec338.docx -------------------------------------------------------------------------------- /Samples/Exec443.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Exec443.docx -------------------------------------------------------------------------------- /Samples/Exec612.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/Exec612.docx -------------------------------------------------------------------------------- /Samples/InvoiceReview143.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/InvoiceReview143.pdf -------------------------------------------------------------------------------- /Samples/bandquiz-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/bandquiz-1.png -------------------------------------------------------------------------------- /Samples/bieber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/bieber.zip -------------------------------------------------------------------------------- /Samples/cimscriptmaker-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-1.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-2.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-3.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-4.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-5.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-6.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-7.png -------------------------------------------------------------------------------- /Samples/cimscriptmaker-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/cimscriptmaker-8.png -------------------------------------------------------------------------------- /Samples/demo-legacyremoting.ps1: -------------------------------------------------------------------------------- 1 | #demo legacy remoting 2 | 3 | #these commands run locally and query remote computers 4 | get-service adws -ComputerName chi-dc04 5 | 6 | #define a variable of domain controller names 7 | $dcs = 'chi-dc04','chi-dc01','chi-dc02' 8 | 9 | get-service adws,dns -ComputerName $dcs | 10 | Select DisplayName,Status,Machinename 11 | 12 | get-process Microsoft* -ComputerName $dcs | 13 | Select Machinename,Name,ID,Handles,VM,WS 14 | 15 | get-eventlog "Active Directory Web Services" -ComputerName $dcs -EntryType Error,Warning -Newest 10 | 16 | Select Machinename,TimeGenerated,EntryType,Message | 17 | out-gridview -title "AD Logs" 18 | -------------------------------------------------------------------------------- /Samples/demo-updatehelp.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/demo-updatehelp.txt -------------------------------------------------------------------------------- /Samples/files.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/files.xml -------------------------------------------------------------------------------- /Samples/foo.dat: -------------------------------------------------------------------------------- 1 | 8/21/2017 4:42:21 PM 2 | 8/21/2017 4:42:43 PM 3 | -------------------------------------------------------------------------------- /Samples/foo2.dat: -------------------------------------------------------------------------------- 1 | 8/21/2017 4:45:10 PM 2 | -------------------------------------------------------------------------------- /Samples/install2.exe: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Samples/jlo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/jlo.zip -------------------------------------------------------------------------------- /Samples/listen.mp3: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Samples/listen.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/listen.zip -------------------------------------------------------------------------------- /Samples/num.dat: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 | 9 10 | 10 11 | 11 12 | 12 13 | 13 14 | 14 15 | 15 16 | 16 17 | 17 18 | 18 19 | 19 20 | 20 21 | 21 22 | 22 23 | 23 24 | 24 25 | 25 26 | 26 27 | 27 28 | 28 29 | 29 30 | 30 31 | 31 32 | 32 33 | 33 34 | 34 35 | 35 36 | 36 37 | 37 38 | 38 39 | 39 40 | 40 41 | 41 42 | 42 43 | 43 44 | 44 45 | 45 46 | 46 47 | 47 48 | 48 49 | 49 50 | 50 51 | 51 52 | 52 53 | 53 54 | 54 55 | 55 56 | 56 57 | 57 58 | 58 59 | 59 60 | 60 61 | 61 62 | 62 63 | 63 64 | 64 65 | 65 66 | 66 67 | 67 68 | 68 69 | 69 70 | 70 71 | 71 72 | 72 73 | 73 74 | 74 75 | 75 76 | 76 77 | 77 78 | 78 79 | 79 80 | 80 81 | 81 82 | 82 83 | 83 84 | 84 85 | 85 86 | 86 87 | 87 88 | 88 89 | 89 90 | 90 91 | 91 92 | 92 93 | 93 94 | 94 95 | 95 96 | 96 97 | 97 98 | 98 99 | 99 100 | 100 101 | -------------------------------------------------------------------------------- /Samples/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/readme.txt -------------------------------------------------------------------------------- /Samples/something.dat: -------------------------------------------------------------------------------- 1 | 123 2 | -------------------------------------------------------------------------------- /Samples/test1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/test1.bmp -------------------------------------------------------------------------------- /Samples/test2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/Samples/test2.bmp -------------------------------------------------------------------------------- /a-note-on-functions.md: -------------------------------------------------------------------------------- 1 | # A Note on Functions 2 | 3 | During my presentation of this material during the 2017 PowerShell and DevOps Summit a question came up about my use of internal and helper functions and their purpose. Due to time constraints I wasn't able to adequately explain what I was doing and why. 4 | 5 | First, the project I was using in my presentation was built in such a way so that I could demonstrate as many concepts as possible within the short session time. What I showed is not necessarily the *right* way to create a class-based tool. 6 | 7 | The use of external functions, helper or otherwise, really fill two needs. If your class has methods, the code within that method can be difficult to test with Pester. And if you have a lot of code, it can be hard to maintain. My recommendation was to implement the heavy-lifting in an external function that is invoked from the method. These were the helper functions that were not exported in the module. The class method might look like this: 8 | 9 | ``` 10 | [timespan]GetAge() { 11 | #invoke the helper function 12 | $r = GetAgeData -name $this.foo 13 | return $r 14 | } 15 | ``` 16 | 17 | Your Pester test can validate the GetAgeData function and this keeps your class definition simple. Think of your class layout as a schema for the type of thing you are creating. 18 | 19 | My other use of functions was to provide wrappers so that end users don't need to know how to create or work with your class programmatically. Instead, you'll create commands that, under the hood, create and work with your class. But the sausage-making bits are all hidden. Instead they have commands with help and parameters and support for WhatIf and all the other things that make PowerShell easy to use. We aren't expected to know how to create a `System.ServiceProcess.ServiceController` object or work its properties and methods. Instead we use commands like `Get-Service `and `Stop-Service`. You are going to do the same thing with your class-based module. 20 | 21 | And to bring everything together, your class **may not need to have any methods defined**. You may simply create functions that do something with an instance of the object. 22 | 23 | The code in this demo project is intended to show you all of these possibilities. In reality, you will most likely take a simpler approach. 24 | 25 | To sum up, the functions you might include in your class-based module will be either private, helper functions for class methods to facilitate Pester testing and public, exported functions to abstract the class so that the user has the same type experience with process or service cmdlets. 26 | 27 | If you have other questions on this topic, please post an Issue in the repository. 28 | 29 | 30 | -------------------------------------------------------------------------------- /file.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Directory: C:\scripts\Demo Class-Based Tools 4 | 5 | 6 | Mode LastWriteTime Length Name 7 | ---- ------------- ------ ---- 8 | d----- 8/1/2017 3:43 PM MyFileObject 9 | d----- 8/1/2017 5:06 PM Samples 10 | -a---- 4/13/2017 7:56 PM 2638 a-note-on-functions.md 11 | -a---- 8/21/2017 3:42 PM 1253 ClassBasics.ps1 12 | -a---- 4/17/2017 10:48 AM 92622 Creating Class-Based PowerShell tools.pdf 13 | -a---- 4/11/2017 5:59 PM 479 Demo1.ps1 14 | -a---- 4/11/2017 6:00 PM 1247 Demo2.ps1 15 | -a---- 4/11/2017 6:02 PM 1865 Demo3.ps1 16 | -a---- 4/11/2017 6:02 PM 3788 Demo4.ps1 17 | -a---- 4/11/2017 6:03 PM 4238 Demo5.ps1 18 | -a---- 8/21/2017 2:53 PM 846 DemoOrder.ps1 19 | -a---- 8/21/2017 3:47 PM 0 file.txt 20 | -a---- 3/16/2017 9:17 AM 899 legacy.ps1 21 | -a---- 3/16/2017 9:14 AM 1118 license.txt 22 | -a---- 8/21/2017 2:53 PM 549 README.md 23 | 24 | 25 | this is something else 26 | -------------------------------------------------------------------------------- /file.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/Demo-Class-Based-Tools/963f5152785f7e63cdc834f949062fb9fdf33654/file.zip -------------------------------------------------------------------------------- /legacy.ps1: -------------------------------------------------------------------------------- 1 | #the legacy approach that we've always used 2 | 3 | $file = get-item .\Demo1.ps1 4 | 5 | #create a hashtable of property names for a new object 6 | $propHash = @{ 7 | Path = $file.FullName 8 | Name = $file.Name 9 | Extension = $file.Extension.Substring(1) 10 | Directory = $file.Directory 11 | Size = $file.Length 12 | Created = $file.CreationTime 13 | Modified = $file.LastWriteTime 14 | } 15 | 16 | #create a custom object 17 | $obj = New-Object -TypeName PSObject -Property $propHash 18 | 19 | #what does it look like? 20 | $obj 21 | $obj | Get-Member 22 | 23 | #insert a type name 24 | $obj.psobject.TypeNames.Insert(0,"myFileObject") 25 | 26 | #add members 27 | $obj | Add-Member -MemberType ScriptMethod -Name GetCreatedAge -Value {(Get-Date) - $this.Created} 28 | $obj | Add-Member -MemberType ScriptMethod -Name GetModifiedAge -Value {(Get-Date) - $this.Modified} 29 | 30 | #look at the object now 31 | $obj 32 | $obj | Get-Member 33 | 34 | #invoke a custom method 35 | $obj.GetCreatedAge().tostring() 36 | 37 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 JDH Information Technology Solutions, Inc. 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | --------------------------------------------------------------------------------