├── Demo-ScheduledJob.ps1 ├── PowerShell Automation Workshop.pdf ├── README.md ├── demos.ps1 └── scripting ├── CreateVolumeReport.ps1 ├── Get-BitsService.ps1 ├── Get-Gitconfig.ps1 ├── Get-MyOS.ps1 ├── HelpDesk ├── HelpDesk.psd1 ├── HelpDesk.psm1 ├── computers.txt ├── docs │ ├── Get-HWInfo.md │ ├── Get-Info.md │ └── Get-VolumeReport.md ├── docs2 │ ├── Get-HWInfo.md │ ├── Get-Info.md │ └── Get-VolumeReport.md ├── en-us │ └── HelpDesk-help.xml ├── functions.ps1 └── vars.ps1 ├── MyInfo.psd1 ├── MyInfo.psm1 ├── blue.css ├── demo-workflowparallel.ps1 ├── demo-workflows.ps1 ├── info2.ps1 ├── info2a.ps1 ├── info3.ps1 ├── info4.ps1 ├── info5.ps1 ├── info6.ps1 ├── infoscript.ps1 └── myTemplates ├── .vscode ├── settings.json └── tasks.json ├── myFunction ├── function-template.ps1 └── plasterManifest.xml ├── myProject ├── License.txt ├── README.md ├── author-note.txt ├── changelog.txt ├── editor │ └── VSCode │ │ ├── settings.json │ │ └── tasks.json ├── module.psm1 ├── plastermanifest.xml └── test │ └── Module.T.ps1 ├── myTemplates.psd1 ├── myTemplates.psm1 ├── readme.txt └── tests └── myproject.tests.ps1 /Demo-ScheduledJob.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | #this will only run under PowerShell running on Windows 4 | 5 | return "This is a walkthrough demo - not a script." 6 | 7 | #demo from a domain member 8 | # enter-pssession -VMName win10 -Credential company\artd 9 | 10 | #This must be run in an elevated session 11 | #Don't confuse this with ScheduledTasks module 12 | 13 | # These commands are in the PSScheduledJob module 14 | get-command -module PSScheduledJob 15 | 16 | # Define when to run the job 17 | help new-jobTrigger 18 | 19 | $trigger = New-JobTrigger -Daily -At 8:00AM 20 | 21 | # Let's see what we created 22 | $trigger 23 | 24 | # Define a name for the scheduled job to save typing 25 | $name = "Domain Controller Service Check" 26 | 27 | # Define a scriptblock. You would normally create a multiline scriptblock. 28 | # I have a one liner for the sake of my demo. 29 | #I want a formatted result I can look at. 30 | $action = { 31 | $services = 'DNS', 'ADWS', 'NTDS', 'KDC' 32 | Get-Service $services -ComputerName dom1| 33 | Sort-object Machinename | Format-Table -GroupBy Machinename -property Name, Displayname, Status 34 | } 35 | 36 | #test the scriptblock 37 | invoke-command $action 38 | 39 | #add some options 40 | $opt = New-ScheduledJobOption -RequireNetwork 41 | 42 | # Register the job 43 | help Register-ScheduledJob 44 | 45 | $paramHash = @{ 46 | Name = $name 47 | ScriptBlock = $action 48 | Trigger = $trigger 49 | ScheduledJobOption = $opt 50 | } 51 | 52 | Register-ScheduledJob @paramHash 53 | 54 | # We can see the scheduled job is now registered 55 | Get-ScheduledJob -Name $name 56 | 57 | # Define a variable for the job path so we don't have to retype 58 | $jobpath = "$env:LocalAppData\Microsoft\Windows\PowerShell\ScheduledJobs" 59 | dir $jobpath -recurse 60 | 61 | # show the job in Task Scheduler Microsoft\Windows\PowerShell\SchedJobs 62 | Taskschd.msc 63 | #find the task with Task Scheduler cmdlets 64 | Get-ScheduledTask -TaskName $name 65 | 66 | # this is a rich object 67 | Get-ScheduledJob -Name $name | get-member 68 | Get-ScheduledJob -Name $name | Select-object * 69 | 70 | # We can also see when it will execute 71 | get-jobtrigger $name 72 | 73 | #or this way 74 | Get-ScheduledJob $name | Get-JobTrigger 75 | 76 | # let's look at options 77 | Get-ScheduledJobOption -Name $name 78 | 79 | # or modify options. Works best in a pipeline 80 | help Set-ScheduledJobOption 81 | 82 | Get-ScheduledJobOption -Name $name | 83 | Set-ScheduledJobOption -RunElevated -passthru | Select Run* 84 | 85 | # we can modify the trigger 86 | help set-jobtrigger 87 | 88 | Get-JobTrigger $Name 89 | 90 | Get-JobTrigger $Name | 91 | Set-JobTrigger -at 12:00PM -PassThru 92 | 93 | #manually kick off in the Task Scheduler 94 | #or use Task Scheduler commands 95 | Get-ScheduledTask $name | Start-ScheduledTask 96 | 97 | # get the job using the standard job cmdlets after it has run 98 | # this may have failed depending on what you are trying to do 99 | Get-Job 100 | Get-ScheduledJob 101 | 102 | # You can also start a scheduled job manually. Notice the new parameter 103 | Start-Job -DefinitionName $Name 104 | 105 | # Now what do we see in the job queue? 106 | Get-Job 107 | 108 | # there are some new properties 109 | Get-Job -Name $name -Newest 1 | Select * 110 | 111 | # get job results 112 | Receive-job $name -keep 113 | 114 | #results written to disk 115 | dir $jobpath -recurse 116 | 117 | #disabling the job 118 | Help Disabled-ScheduledJob 119 | Disable-ScheduledJob $name -WhatIf 120 | Disable-ScheduledJob $name -PassThru 121 | 122 | #if I wanted to re-enable 123 | Enable-ScheduledJob $name -WhatIf 124 | 125 | #And finally we'll remove the scheduled job 126 | help Unregister-ScheduledJob 127 | Unregister-ScheduledJob -Name $name 128 | 129 | Get-ScheduledJob 130 | 131 | #also clears job queue 132 | Get-job 133 | 134 | #UNREGISTERING ALSO DELETES HISTORY AND OUTPUT 135 | dir $jobpath -recurse 136 | 137 | #accessing network resources requires credential 138 | get-content C:\scripts\volreport.ps1 139 | 140 | $params = @{ 141 | Name = "VolumeReport" 142 | Trigger = (New-JobTrigger -At 6:00 -Weekly -DaysOfWeek Monday, Wednesday, Friday) 143 | MaxResultcount = 5 144 | ScheduledJobOption = (New-ScheduledJobOption -RunElevated -RequireNetwork) 145 | Filepath = "C:\scripts\volreport.ps1" 146 | Credential = (Get-Credential company\artd) 147 | Argumentlist = @(@("dom1", "srv2", "srv1"), "c:\work\volume.csv") 148 | InitializationScript = { Import-module Storage} 149 | } 150 | 151 | register-scheduledjob @params 152 | 153 | get-scheduledtask $params.name | Start-ScheduledTask 154 | # start-job -DefinitionName $params.name | wait-job 155 | 156 | #wait a minute 157 | Get-Job 158 | #my job doesn't really write a result. 159 | import-csv C:\work\volume.csv 160 | 161 | Unregister-ScheduledJob $params.Name 162 | del C:\work\volume.csv 163 | 164 | #again, these are the commands you'll use 165 | get-command -module PSScheduledJob 166 | 167 | #read help 168 | help about_scheduled 169 | #or read the raw text file 170 | dir C:\windows\system32\WindowsPowerShell\v1.0\Modules\PSScheduledJob\en-US 171 | psedit C:\windows\system32\WindowsPowerShell\v1.0\Modules\PSScheduledJob\en-US\about_Scheduled_Jobs_Basics.help.txt 172 | 173 | cls 174 | 175 | -------------------------------------------------------------------------------- /PowerShell Automation Workshop.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PSAutomationWorkshop/bd6e400b1dd8a8421ceca300096e5c4c6490c501/PowerShell Automation Workshop.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell Automation Workshop 2 | 3 | The files in this repository are teaching material for my PowerShell Automation Workshop 4 | delivered at the PowerShell Saturday pre-conference in Chattanooga, TN in August 2018. 5 | 6 | All PowerShell scripts and code samples are offered "as-is" and are intended to be used for 7 | educational purposes. There is no guarantee that all code examples will work properly, or work 8 | properly in your environment. 9 | 10 | _Nothing in this repository is intended to be used as written in a production environment._ 11 | -------------------------------------------------------------------------------- /demos.ps1: -------------------------------------------------------------------------------- 1 | write-warning 'This is a set of walk through demos, not a script you doofus.' 2 | return 3 | 4 | 5 | #region Managing at Scale 6 | 7 | $computers = "dom1","srv1","srv2","win10","srv4" 8 | 9 | Measure-Command { 10 | $r = Get-service bits,winrm -ComputerName $computers | 11 | Select Name,Status,Machinename 12 | } 13 | 14 | $r 15 | 16 | Measure-Command { 17 | #some additional overhead here 18 | $r = Invoke-Command { get-service bits,winrm} -computername $computers | 19 | Select Name,Status,PSComputername 20 | 21 | } 22 | 23 | $r 24 | 25 | $s = new-pssession -ComputerName $computers 26 | Measure-Command { 27 | #some additional overhead here 28 | $r = Invoke-Command { get-service bits,winrm} -session $s | 29 | Select Name,Status,PSComputername 30 | 31 | } 32 | 33 | Remove-PSSession $s 34 | 35 | #repeat with a problem 36 | $computers = "dom1","srv1","srv2","srv3","win10","srv4" 37 | 38 | #improve with sessions 39 | #leverage the pipeline 40 | #make remote servers do the work 41 | 42 | $computers = "dom1","srv1","srv2","srv4" 43 | 44 | #you could do this 45 | $data = foreach ($computer in $computers) { 46 | Get-ciminstance -ClassName Win32_OperatingSystem -ComputerName $computer 47 | } 48 | 49 | $data | Select @{Name="Computername";Expression={$_.CSName}}, 50 | @{Name="OS";Expression={$_.Caption}},LastBootUptime, 51 | @{Name="Uptime";Expression = { (Get-Date) - $_.LastbootupTime}} | 52 | Sort Uptime -Descending 53 | 54 | Invoke-Command { 55 | Get-ciminstance -ClassName Win32_OperatingSystem | 56 | Select @{Name="Computername";Expression={$_.CSName}}, 57 | @{Name="OS";Expression={$_.Caption}},LastBootUptime, 58 | @{Name="Uptime";Expression = { (Get-Date) - $_.LastbootupTime}} 59 | } -HideComputerName -computername $computers | 60 | Select -Property * -ExcludeProperty runspaceID | Sort Uptime -Descending 61 | 62 | 63 | #endregion 64 | 65 | #region Command to Tool 66 | 67 | #take GetInfo.ps1 from interactive command to tool 68 | psedit .\scripting\infoscript.ps1 69 | psedit .\scripting\info6.ps1 70 | 71 | #modules 72 | dir .\scripting\HelpDesk 73 | code .\scripting\HelpDesk 74 | #explore module structure 75 | #Platyps documentation 76 | #demo 77 | 78 | #endregion 79 | 80 | #region Controller Scripts 81 | 82 | #create an HTML report based on HelpDesk tools 83 | psedit .\scripting\CreateVolumeReport.ps1 84 | start .\DiskReport.htm 85 | 86 | #endregion 87 | 88 | #region Proxy and Wrappers 89 | 90 | Find-Module psscripttools 91 | Import-Module PSScriptTools 92 | Get-Command -Module PSScriptTools 93 | help copy-command 94 | 95 | Copy-Command -Command Get-Service -NewName Get-BitsService 96 | psedit .\scripting\Get-BitsService.ps1 97 | 98 | Copy-Command -Command Get-Ciminstance -NewName Get-MyOS -IncludeDynamic -AsProxy -UseForwardHelp 99 | 100 | psedit .\scripting\Get-MyOS.ps1 101 | 102 | #endregion 103 | 104 | #region Scheduled Jobs 105 | psedit .\Demo-ScheduledJob.ps1 106 | 107 | #endregion 108 | 109 | #region Plaster Templates 110 | 111 | #https://github.com/powershell/plaster 112 | find-module plaster 113 | 114 | get-command -Module plaster 115 | Get-PlasterTemplate 116 | 117 | help New-PlasterManifest 118 | #must be this file name 119 | $new = "c:\work\plastermanifest.xml" 120 | $params = @{ 121 | Path = $new 122 | TemplateName = "MyTool" 123 | TemplateType = "Project" 124 | Title = "My Tool" 125 | Description = "Scaffold a MyTool project" 126 | Author = "Jeff Hicks" 127 | Tags = "module" 128 | TemplateVersion = "0.0.1" 129 | } 130 | New-PlasterManifest @params 131 | psedit $new 132 | 133 | code .\scripting\myTemplates 134 | 135 | #mytemplates has been copied to Programfiles 136 | Get-PlasterTemplate -IncludeInstalledModules | tee -Variable t 137 | 138 | Invoke-Plaster -TemplatePath $t[2].TemplatePath C:\scripts\PSChatt 139 | 140 | #add a command 141 | Invoke-Plaster -TemplatePath $t[3].TemplatePath -DestinationPath c:\scripts\PSChatt 142 | psedit $t[3].TemplatePath 143 | 144 | #you can also skip interactive 145 | $hash = @{ 146 | TemplatePath = $t[3].TemplatePath 147 | DestinationPath = "c:\scripts\pschatt" 148 | #these are template parameters 149 | Name = "Set-Magic" 150 | Version = "0.1.0" 151 | OutputType = "[PSCustomobject]" 152 | ShouldProcess = "yes" 153 | Help = "no" 154 | Computername = "yes" 155 | Force = $True 156 | NoLogo = $True 157 | } 158 | 159 | Invoke-Plaster @hash 160 | #open new project in VS Code 161 | code c:\scripts\pschatt 162 | 163 | #reset demo 164 | # del c:\scripts\pschatt -Recurse -force 165 | 166 | #endregion 167 | 168 | #region PowerShell Workflow 169 | 170 | psedit .\demo-workflows.ps1 171 | psedit .\demo-workflowparallel.ps1 172 | 173 | #endregion 174 | 175 | #region Desired State Configuration 176 | 177 | #walk through concepts 178 | #create and deploy a config to SRV4 179 | 180 | #endregion 181 | 182 | -------------------------------------------------------------------------------- /scripting/CreateVolumeReport.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | #requires -module Storage 3 | 4 | #create a disk space report 5 | [cmdletbinding()] 6 | Param( 7 | #the name of the html file. Do not specify the path 8 | [string]$Path = "DiskReport.htm" 9 | ) 10 | 11 | #manually import the module because it isn't part of my 12 | #usual %PSMODULEPATH% which you would use. 13 | Import-Module $PSScriptRoot\helpdesk\helpdesk.psd1 14 | 15 | $Computername = $domaincomputers 16 | 17 | #initialize an array 18 | $fragments = @("

Company.pri

") 19 | 20 | $progParam = @{ 21 | Activity = "Domain Volume Report" 22 | Status = "Querying domain members" 23 | Percentcomplete = 0 24 | CurrentOperation = "" 25 | } 26 | 27 | #initialize a counter for the progress bar 28 | $i = 0 29 | 30 | foreach ($computer in $Computername) { 31 | $i++ 32 | $progParam.CurrentOperation = $Computer 33 | 34 | $progparam.percentcomplete = ($i / $computername.count) * 100 35 | Write-Progress @progParam 36 | 37 | Try { 38 | 39 | $disk = Get-volumeReport -computername $computer 40 | 41 | $fragments += "

$($computer.toUpper())

" 42 | $fragments += $disk | Select-object -property DriveLetter, HealthStatus, 43 | @{Name = "SizeGB"; Expression = {$_.size / 1gb -as [int]}}, 44 | @{Name = "RemainingGB"; Expression = {$_.sizeremaining / 1gb }} | 45 | ConvertTo-Html 46 | 47 | } 48 | Catch { 49 | Write-warning "$_.Exception.message" 50 | } 51 | } #foreach 52 | 53 | If ($fragments.count -gt 0) { 54 | 55 | $head = @" 56 | Domain Volume Report 57 | 94 | "@ 95 | 96 | $footer = @" 97 |
Run date: $(Get-Date)
98 | Computer: $env:computername
99 | Script: $((get-item $myinvocation.InvocationName).fullname)
100 | "@ 101 | 102 | #define a hashtable of parameters to splat to ConvertTo-Html 103 | $cParams = @{ 104 | Head = $head 105 | Body = $fragments 106 | PostContent = $footer 107 | } 108 | 109 | #create the HTML and save it to a file 110 | ConvertTo-Html @cParams | Out-File -FilePath $path -Encoding ascii 111 | Write-Host "See $path for your report." -ForegroundColor green 112 | } -------------------------------------------------------------------------------- /scripting/Get-BitsService.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | 4 | <# 5 | This is a copy of: 6 | 7 | CommandType Name Version Source 8 | ----------- ---- ------- ------ 9 | Cmdlet Get-Service 3.1.0.0 Microsoft.PowerShell.Management 10 | 11 | Created: 06 August 2018 12 | Author : jeff 13 | 14 | #> 15 | 16 | 17 | Function Get-BitsService { 18 | <# 19 | 20 | .SYNOPSIS 21 | 22 | Gets the services on a local or remote computer. 23 | 24 | 25 | .DESCRIPTION 26 | 27 | The Get-BitsService cmdlet gets the Bits service 28 | 29 | .PARAMETER ComputerName 30 | 31 | Gets the services running on the specified computers. The default is the local computer. 32 | 33 | Type the NetBIOS name, an IP address, or a fully qualified domain name (FQDN) of a remote computer. To specify the local computer, type the computer name, a dot (.), or localhost. 34 | 35 | This parameter does not rely on Windows PowerShell remoting. You can use the ComputerName parameter of Get-BitsService even if your computer is not configured to run remote commands. 36 | 37 | PS C:\>Get-BitsService 38 | 39 | .EXAMPLE 40 | 41 | PS C:\>Get-BitsService $domaincomputers 42 | 43 | This command retrieves the bits services from the $domaincomputers variable. 44 | 45 | 46 | .INPUTS 47 | 48 | System.String 49 | 50 | 51 | .OUTPUTS 52 | 53 | System.ServiceProcess.ServiceController 54 | 55 | .LINK 56 | 57 | New-Service 58 | 59 | .LINK 60 | 61 | Restart-Service 62 | 63 | .LINK 64 | 65 | Resume-Service 66 | 67 | .LINK 68 | 69 | Set-Service 70 | 71 | .LINK 72 | 73 | Start-Service 74 | 75 | .LINK 76 | 77 | Stop-Service 78 | 79 | .LINK 80 | 81 | Suspend-Service 82 | 83 | #> 84 | [CmdletBinding()] 85 | [Alias("gbs")] 86 | Param( 87 | 88 | [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true)] 89 | [Alias('Cn')] 90 | [ValidateNotNullOrEmpty()] 91 | [string[]]$ComputerName = $env:computername 92 | 93 | ) 94 | 95 | Begin { 96 | 97 | Write-Verbose "[BEGIN ] Starting $($MyInvocation.Mycommand)" 98 | Write-Verbose "[BEGIN ] Using parameter set $($PSCmdlet.ParameterSetName)" 99 | Write-Verbose ($PSBoundParameters | Out-String) 100 | 101 | } #begin 102 | 103 | Process { 104 | $PSBoundParameters.add("Name", "bits") 105 | if (-Not $PSBoundParameters.ContainsKey("Computername")) { 106 | $PSBoundParameters.add("Computername", $computername) 107 | } 108 | Get-Service @PSBoundParameters | 109 | Select-Object -Property @{Name = "Computername"; expression = {$_.MachineName.toUpper()}}, 110 | Name, Status, StartType 111 | 112 | } #process 113 | 114 | End { 115 | 116 | Write-Verbose "[END ] Ending $($MyInvocation.Mycommand)" 117 | 118 | } #end 119 | 120 | } #end function Get-BitsService -------------------------------------------------------------------------------- /scripting/Get-Gitconfig.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | Function Get-GitConfig { 4 | 5 | <# 6 | .SYNOPSIS 7 | Get git configuration settings 8 | .DESCRIPTION 9 | Git stores configurations settings in a simple text file format. Fortunately, this file is structured and predictable. This command will process git configuration information into PowerShell friendly output. 10 | .PARAMETER Scope 11 | Possible values are Global,Local or System 12 | .PARAMETER Path 13 | Enter the path to a .gitconfig file. You can use shell paths like ~\.gitconfig 14 | .EXAMPLE 15 | PS C:\> Get-GitConfig 16 | 17 | Scope Category Name Setting 18 | ----- -------- ---- ------- 19 | global filter lfs git-lfs clean -- %f 20 | global filter lfs git-lfs smudge -- %f 21 | global filter lfs true 22 | global user name Art Deco 23 | global user email artd@company.com 24 | global gui recentrepo C:/Scripts/Gen2Tools 25 | global gui recentrepo C:/Scripts/PSVirtualBox 26 | global gui recentrepo C:/Scripts/FormatFunctions 27 | global core editor powershell_ise.exe 28 | global core autocrlf true 29 | global core excludesfile ~/.gitignore 30 | global push default simple 31 | global color ui true 32 | global alias logd log --oneline --graph --decorate 33 | global alias last log -1 HEAD 34 | global alias pushdev !git checkout master && git merge dev && git push && git checkout dev 35 | global alias st status 36 | global alias fp !git fetch && git pull 37 | global merge tool kdiff3 38 | global mergetool kdiff3 'C:/Program Files/KDiff3/kdiff3.exe' $BASE $LOCAL $REMOTE -o $MERGED 39 | 40 | Getting global configuration settings 41 | 42 | .EXAMPLE 43 | PS C:\> Get-GitConfig -scope system | where category -eq 'filter' 44 | 45 | Scope Category Name Setting 46 | ----- -------- ---- ------- 47 | system filter lfs git-lfs clean -- %f 48 | system filter lfs git-lfs smudge -- %f 49 | system filter lfs git-lfs filter-process 50 | system filter lfs true 51 | 52 | Get system configuration and only git filters. 53 | 54 | .EXAMPLE 55 | PS C:\> Get-GitConfig -path ~\.gitconfig | format-table -groupby category -property Name,Setting 56 | 57 | Get settings from a configuration file and present in a grouped, formatted table. 58 | 59 | .INPUTS 60 | none 61 | .OUTPUTS 62 | [pscustomobject] 63 | .NOTES 64 | The command assumes you have git installed. Otherwise, why would you be using this? 65 | 66 | Last updated: 11 May, 2018 67 | .LINK 68 | git 69 | #> 70 | 71 | [CmdletBinding(DefaultParameterSetName = "default")] 72 | [OutputType([PSCustomObject])] 73 | Param ( 74 | [Parameter(Position = 0, ParameterSetName = "default")] 75 | [ValidateSet("Global", "System", "Local")] 76 | [string[]]$Scope = "Global", 77 | 78 | [Parameter(ParameterSetName = "file")] 79 | #the path to a .gitconfig file which must be specified if scope is File 80 | [ValidateScript( {Test-Path $_})] 81 | [Alias("config")] 82 | [string]$Path 83 | ) 84 | 85 | Begin { 86 | Write-Verbose "Starting $($myinvocation.MyCommand)" 87 | if ($path) { 88 | #convert path value to a complete file system path 89 | $Path = Convert-Path -Path $path 90 | } 91 | #internal helper function 92 | function _process { 93 | [cmdletbinding()] 94 | Param( 95 | [scriptblock]$scriptblock, 96 | [string]$Scope 97 | ) 98 | 99 | Write-Verbose "Invoking $($scriptblock.tostring())" 100 | #invoke the scriptblock and save the text output 101 | $data = Invoke-Command -scriptblock $scriptblock 102 | 103 | #split each line of the config on the = sign 104 | #and add to the hashtable 105 | foreach ($line in $data) { 106 | $split = $line.split("=") 107 | #split the first element again to get the category and name 108 | $sub = $split[0].split(".") 109 | [PSCustomObject]@{ 110 | Scope = $scope 111 | Category = $sub[0] 112 | Name = $sub[1] 113 | Setting = $split[1] 114 | } 115 | } #foreach line 116 | } # _process 117 | } #begin 118 | 119 | Process { 120 | 121 | if ($PSCmdlet.ParameterSetName -eq 'file') { 122 | Write-Verbose "Getting config from $path" 123 | $get = [scriptblock]::Create("git config --file $path --list") 124 | #call the helper function 125 | _process -scriptblock $get -scope "File" 126 | 127 | } 128 | else { 129 | foreach ($item in $Scope) { 130 | Write-Verbose "Getting $item config" 131 | #the git command is case sensitive so make the scope 132 | #lower case 133 | $item = $item.tolower() 134 | 135 | #create a scriptblock to run git config 136 | $get = [scriptblock]::Create("git config --$item --list") 137 | 138 | #call the helper function 139 | _process -scriptblock $get -Scope $item 140 | 141 | } #foreach scope 142 | } #else 143 | } #process 144 | 145 | End { 146 | Write-Verbose "Ending $($myinvocation.MyCommand)" 147 | } #end 148 | 149 | } #end Get-GitConfig 150 | 151 | #add an optional alias 152 | Set-Alias -Name gcc -Value Get-GitConfig -------------------------------------------------------------------------------- /scripting/Get-MyOS.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | #requires -module CimCmdlets 3 | 4 | <# 5 | This is a copy of: 6 | 7 | CommandType Name Version Source 8 | ----------- ---- ------- ------ 9 | Cmdlet Get-CimInstance 1.0.0.0 CimCmdlets 10 | 11 | Created: 06 August 2018 12 | Author : jeff 13 | 14 | #> 15 | Function Get-MyOS { 16 | 17 | <# 18 | insert comment based help 19 | #> 20 | 21 | 22 | [CmdletBinding(DefaultParameterSetName = 'ClassNameComputerSet')] 23 | param( 24 | [Parameter(ParameterSetName = 'CimInstanceSessionSet', ValueFromPipeline = $true)] 25 | [ValidateNotNullOrEmpty()] 26 | [Microsoft.Management.Infrastructure.CimSession[]]$CimSession, 27 | 28 | [Parameter(ParameterSetName = 'ClassNameComputerSet', Position = 0, ValueFromPipelineByPropertyName = $true)] 29 | [Alias('CN', 'ServerName')] 30 | [ValidateNotNullOrEmpty()] 31 | [string[]]$ComputerName = $env:computername, 32 | 33 | [Alias('OT')] 34 | [uint32]$OperationTimeoutSec 35 | 36 | ) 37 | begin { 38 | try { 39 | $outBuffer = $null 40 | if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) { 41 | $PSBoundParameters['OutBuffer'] = 1 42 | } 43 | 44 | #ADD parameters 45 | $PSBoundParameters.Add("Namespace", "Root\CimV2") 46 | $PSBoundParameters.Add("Classname", "Win32_OperatingSystem") 47 | 48 | #the function invokes the full Get-Ciminstance command 49 | $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('CimCmdlets\Get-CimInstance', [System.Management.Automation.CommandTypes]::Cmdlet) 50 | 51 | #MODIFIED SCRIPT COMMAND 52 | $scriptCmd = {& $wrappedCmd @PSBoundParameters | 53 | Select-object -property @{Name = "Computername"; Expression = {$_.CSName}}, 54 | @{Name = "FullName"; Expression = { $_.Caption}}, 55 | Version, BuildNumber, InstallDate, OSArchitecture } 56 | 57 | $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin) 58 | $steppablePipeline.Begin($PSCmdlet) 59 | } 60 | catch { 61 | throw 62 | } 63 | } 64 | 65 | process { 66 | try { 67 | $steppablePipeline.Process($_) 68 | } 69 | catch { 70 | throw 71 | } 72 | } 73 | 74 | end { 75 | try { 76 | $steppablePipeline.End() 77 | } 78 | catch { 79 | throw 80 | } 81 | } 82 | 83 | } #end function Get-MyOS -------------------------------------------------------------------------------- /scripting/HelpDesk/HelpDesk.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PSAutomationWorkshop/bd6e400b1dd8a8421ceca300096e5c4c6490c501/scripting/HelpDesk/HelpDesk.psd1 -------------------------------------------------------------------------------- /scripting/HelpDesk/HelpDesk.psm1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | #dot source supporting files 4 | . $PSScriptRoot\functions.ps1 5 | . $PSScriptroot\vars.ps1 6 | 7 | #define an alias 8 | Set-Alias -Name gn -Value Get-Info 9 | 10 | #Export-ModuleMember -Function Get-Info,Get-Hwinfo -Alias gn -Variable helpdesk 11 | #there is a bug where variables won't get exported from the manifest 12 | Export-ModuleMember -Variable helpdesk,domaincomputers -------------------------------------------------------------------------------- /scripting/HelpDesk/computers.txt: -------------------------------------------------------------------------------- 1 | DOM1 2 | SRV1 3 | SRV2 4 | WIN10 -------------------------------------------------------------------------------- /scripting/HelpDesk/docs/Get-HWInfo.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-HWInfo 9 | 10 | ## SYNOPSIS 11 | 12 | Get hardware information. 13 | 14 | ## SYNTAX 15 | 16 | ```yaml 17 | Get-HWInfo [[-Computername] ] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Get server hardware detail 23 | 24 | ## EXAMPLES 25 | 26 | ### Example 1 27 | 28 | ```powershell 29 | PS C:\> get-hwinfo srv1 30 | 31 | Name Version OS FreeGB 32 | ---- ------- -- ------ 33 | SRV1 v2.0.0 Windows Unicorn 7.97 34 | ``` 35 | 36 | ## PARAMETERS 37 | 38 | ### -Computername 39 | 40 | The name of a computer to check. 41 | 42 | ```yaml 43 | Type: String 44 | Parameter Sets: (All) 45 | Aliases: 46 | 47 | Required: False 48 | Position: 0 49 | Default value: None 50 | Accept pipeline input: False 51 | Accept wildcard characters: False 52 | ``` 53 | 54 | ### CommonParameters 55 | 56 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 57 | 58 | ## INPUTS 59 | 60 | ### None 61 | 62 | ## OUTPUTS 63 | 64 | ### System.Object 65 | 66 | ## NOTES 67 | 68 | ## RELATED LINKS 69 | 70 | [Get-Info]() -------------------------------------------------------------------------------- /scripting/HelpDesk/docs/Get-Info.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-Info 9 | 10 | ## SYNOPSIS 11 | 12 | Get help desk server information 13 | 14 | ## SYNTAX 15 | 16 | ```yaml 17 | Get-Info [[-Computername] ] [-Credential ] [-LogFailures] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Use this command to get basic server information. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | 28 | ```powershell 29 | PS C:\> Get-Info SRV1 30 | 31 | 32 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 33 | Version : 10.0.14393 34 | Uptime : 1.05:39:22.5945412 35 | MemoryGB : 1 36 | PhysicalProcessors : 1 37 | LogicalProcessors : 1 38 | ComputerName : SRV1 39 | ``` 40 | 41 | ## PARAMETERS 42 | 43 | ### -Computername 44 | 45 | The name of the computer to query. You must have admin rights. 46 | 47 | ```yaml 48 | Type: String[] 49 | Parameter Sets: (All) 50 | Aliases: cn 51 | 52 | Required: False 53 | Position: 1 54 | Default value: $env:computername 55 | Accept pipeline input: True (ByPropertyName, ByValue) 56 | Accept wildcard characters: False 57 | ``` 58 | 59 | ### -LogFailures 60 | 61 | Create a log file of computers that failed. 62 | 63 | ```yaml 64 | Type: SwitchParameter 65 | Parameter Sets: (All) 66 | Aliases: 67 | 68 | Required: False 69 | Position: Named 70 | Default value: False 71 | Accept pipeline input: False 72 | Accept wildcard characters: False 73 | ``` 74 | 75 | ### -Credential 76 | 77 | Specify an alternate credential. 78 | 79 | ```yaml 80 | Type: PSCredential 81 | Parameter Sets: (All) 82 | Aliases: 83 | 84 | Required: False 85 | Position: Named 86 | Default value: None 87 | Accept pipeline input: False 88 | Accept wildcard characters: False 89 | ``` 90 | 91 | ### CommonParameters 92 | 93 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 94 | 95 | ## INPUTS 96 | 97 | ## OUTPUTS 98 | 99 | ## NOTES 100 | 101 | Last Updated: 6 August, 2018 102 | 103 | ## RELATED LINKS 104 | 105 | [Get-CimInstance]() 106 | 107 | [Get-HWInfo]() 108 | 109 | [Get-VolumeReport]() -------------------------------------------------------------------------------- /scripting/HelpDesk/docs/Get-VolumeReport.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-VolumeReport 9 | 10 | ## SYNOPSIS 11 | 12 | Get volume report summary information. 13 | 14 | ## SYNTAX 15 | 16 | ### computer (Default) 17 | 18 | ```yaml 19 | Get-VolumeReport [[-Computername] ] [-Drive ] [] 20 | ``` 21 | 22 | ### session 23 | 24 | ```yaml 25 | Get-VolumeReport -Cimsession [-Drive ] [] 26 | ``` 27 | 28 | ## DESCRIPTION 29 | 30 | Use this command to get summary information about a storage volume on a remote server. 31 | 32 | ## EXAMPLES 33 | 34 | ### Example 1 35 | 36 | ```powershell 37 | PS C:\> Get-VolumeReport -computername SRV7 38 | 39 | Driveletter : C 40 | Size : 254721126400 41 | SizeRemaining : 11003076608 42 | HealthStatus : Healthy 43 | Date : 8/6/2018 12:10:10 PM 44 | Computername : SRV7 45 | ``` 46 | 47 | Get volume summary information for drive C on SRV7. 48 | 49 | ## PARAMETERS 50 | 51 | ### -Cimsession 52 | 53 | A Cimsession object to a remote computer. 54 | 55 | ```yaml 56 | Type: CimSession[] 57 | Parameter Sets: session 58 | Aliases: 59 | 60 | Required: True 61 | Position: Named 62 | Default value: None 63 | Accept pipeline input: True (ByValue) 64 | Accept wildcard characters: False 65 | ``` 66 | 67 | ### -Computername 68 | 69 | Enter a computername 70 | 71 | ```yaml 72 | Type: String 73 | Parameter Sets: computer 74 | Aliases: 75 | 76 | Required: False 77 | Position: 0 78 | Default value: Local host 79 | Accept pipeline input: True (ByPropertyName, ByValue) 80 | Accept wildcard characters: False 81 | ``` 82 | 83 | ### -Drive 84 | 85 | Enter a drive letter like C or D without the colon. 86 | 87 | ```yaml 88 | Type: String 89 | Parameter Sets: (All) 90 | Aliases: 91 | 92 | Required: False 93 | Position: Named 94 | Default value: C 95 | Accept pipeline input: False 96 | Accept wildcard characters: False 97 | ``` 98 | 99 | ### CommonParameters 100 | 101 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. 102 | For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 103 | 104 | ## INPUTS 105 | 106 | ### Microsoft.Management.Infrastructure.CimSession[] 107 | 108 | ### System.String 109 | 110 | ## OUTPUTS 111 | 112 | ### System.Object 113 | 114 | ## NOTES 115 | 116 | ## RELATED LINKS 117 | 118 | [Get-HWInfo]() -------------------------------------------------------------------------------- /scripting/HelpDesk/docs2/Get-HWInfo.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-HWInfo 9 | 10 | ## SYNOPSIS 11 | Get hardware information. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-HWInfo [[-Computername] ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Get server hardware detail 21 | 22 | ## EXAMPLES 23 | 24 | ### Example 1 25 | ``` 26 | PS C:\> get-hwinfo srv1 27 | 28 | Name Version OS FreeGB 29 | ---- ------- -- ------ 30 | SRV1 v2.0.0 Windows Unicorn 7.97 31 | ``` 32 | 33 | ## PARAMETERS 34 | 35 | ### -Computername 36 | The name of a computer to check. 37 | 38 | ```yaml 39 | Type: String 40 | Parameter Sets: (All) 41 | Aliases: 42 | 43 | Required: False 44 | Position: 0 45 | Default value: None 46 | Accept pipeline input: False 47 | Accept wildcard characters: False 48 | ``` 49 | 50 | ### CommonParameters 51 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. 52 | For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 53 | 54 | ## INPUTS 55 | 56 | ### None 57 | 58 | ## OUTPUTS 59 | 60 | ### System.Object 61 | 62 | ## NOTES 63 | 64 | ## RELATED LINKS 65 | 66 | [Get-Info]() 67 | 68 | -------------------------------------------------------------------------------- /scripting/HelpDesk/docs2/Get-Info.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-Info 9 | 10 | ## SYNOPSIS 11 | Get help desk server information 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-Info [[-Computername] ] [-Credential ] [-LogFailures] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Use this command to get basic server information. 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | PS C:\> Get-Info SRV1 27 | 28 | 29 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 30 | Version : 10.0.14393 31 | Uptime : 1.05:39:22.5945412 32 | MemoryGB : 1 33 | PhysicalProcessors : 1 34 | LogicalProcessors : 1 35 | ComputerName : SRV1 36 | ``` 37 | 38 | ## PARAMETERS 39 | 40 | ### -Computername 41 | The name of the computer to query. 42 | You must have admin rights. 43 | 44 | ```yaml 45 | Type: String[] 46 | Parameter Sets: (All) 47 | Aliases: cn 48 | 49 | Required: False 50 | Position: 1 51 | Default value: $env:computername 52 | Accept pipeline input: True (ByPropertyName, ByValue) 53 | Accept wildcard characters: False 54 | ``` 55 | 56 | ### -LogFailures 57 | Create a log file of computers that failed. 58 | 59 | ```yaml 60 | Type: SwitchParameter 61 | Parameter Sets: (All) 62 | Aliases: 63 | 64 | Required: False 65 | Position: Named 66 | Default value: False 67 | Accept pipeline input: False 68 | Accept wildcard characters: False 69 | ``` 70 | 71 | ### -Credential 72 | Specify an alternate credential. 73 | 74 | ```yaml 75 | Type: PSCredential 76 | Parameter Sets: (All) 77 | Aliases: 78 | 79 | Required: False 80 | Position: Named 81 | Default value: None 82 | Accept pipeline input: False 83 | Accept wildcard characters: False 84 | ``` 85 | 86 | ### CommonParameters 87 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. 88 | For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 89 | 90 | ## INPUTS 91 | 92 | ## OUTPUTS 93 | 94 | ## NOTES 95 | Last Updated: 6 August, 2018 96 | 97 | ## RELATED LINKS 98 | 99 | [Get-CimInstance]() 100 | 101 | [Get-HWInfo]() 102 | 103 | [Get-VolumeReport]() 104 | 105 | -------------------------------------------------------------------------------- /scripting/HelpDesk/docs2/Get-VolumeReport.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: HelpDesk-help.xml 3 | Module Name: HelpDesk 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-VolumeReport 9 | 10 | ## SYNOPSIS 11 | Get volume report summary information. 12 | 13 | ## SYNTAX 14 | 15 | ### computer (Default) 16 | ``` 17 | Get-VolumeReport [[-Computername] ] [-Drive ] [] 18 | ``` 19 | 20 | ### session 21 | ``` 22 | Get-VolumeReport -Cimsession [-Drive ] [] 23 | ``` 24 | 25 | ## DESCRIPTION 26 | Use this command to get summary information about a storage volume on a remote server. 27 | 28 | ## EXAMPLES 29 | 30 | ### Example 1 31 | ``` 32 | PS C:\> Get-VolumeReport -computername SRV7 33 | 34 | Driveletter : C 35 | Size : 254721126400 36 | SizeRemaining : 11003076608 37 | HealthStatus : Healthy 38 | Date : 8/6/2018 12:10:10 PM 39 | Computername : SRV7 40 | ``` 41 | 42 | Get volume summary information for drive C on SRV7. 43 | 44 | ## PARAMETERS 45 | 46 | ### -Cimsession 47 | A Cimsession object to a remote computer. 48 | 49 | ```yaml 50 | Type: CimSession[] 51 | Parameter Sets: session 52 | Aliases: 53 | 54 | Required: True 55 | Position: Named 56 | Default value: None 57 | Accept pipeline input: True (ByValue) 58 | Accept wildcard characters: False 59 | ``` 60 | 61 | ### -Computername 62 | Enter a computername 63 | 64 | ```yaml 65 | Type: String 66 | Parameter Sets: computer 67 | Aliases: 68 | 69 | Required: False 70 | Position: 0 71 | Default value: Local host 72 | Accept pipeline input: True (ByPropertyName, ByValue) 73 | Accept wildcard characters: False 74 | ``` 75 | 76 | ### -Drive 77 | Enter a drive letter like C or D without the colon. 78 | 79 | ```yaml 80 | Type: String 81 | Parameter Sets: (All) 82 | Aliases: 83 | 84 | Required: False 85 | Position: Named 86 | Default value: C 87 | Accept pipeline input: False 88 | Accept wildcard characters: False 89 | ``` 90 | 91 | ### CommonParameters 92 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. 93 | For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 94 | 95 | ## INPUTS 96 | 97 | ### Microsoft.Management.Infrastructure.CimSession[] 98 | 99 | ### System.String 100 | 101 | ## OUTPUTS 102 | 103 | ### System.Object 104 | 105 | ## NOTES 106 | 107 | ## RELATED LINKS 108 | 109 | [Get-HWInfo]() 110 | 111 | -------------------------------------------------------------------------------- /scripting/HelpDesk/en-us/HelpDesk-help.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Get-HWInfo 6 | Get 7 | HWInfo 8 | 9 | Get hardware information. 10 | 11 | 12 | 13 | Get server hardware detail 14 | 15 | 16 | 17 | Get-HWInfo 18 | 19 | Computername 20 | 21 | The name of a computer to check. 22 | 23 | String 24 | 25 | String 26 | 27 | 28 | None 29 | 30 | 31 | 32 | 33 | 34 | Computername 35 | 36 | The name of a computer to check. 37 | 38 | String 39 | 40 | String 41 | 42 | 43 | None 44 | 45 | 46 | 47 | 48 | 49 | None 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | System.Object 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------- Example 1 -------------------------- 74 | PS C:\> get-hwinfo srv1 75 | 76 | Name Version OS FreeGB 77 | ---- ------- -- ------ 78 | SRV1 v2.0.0 Windows Unicorn 7.97 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Get-Info 87 | 88 | 89 | 90 | 91 | 92 | 93 | Get-Info 94 | Get 95 | Info 96 | 97 | Get help desk server information 98 | 99 | 100 | 101 | Use this command to get basic server information. 102 | 103 | 104 | 105 | Get-Info 106 | 107 | Computername 108 | 109 | The name of the computer to query. You must have admin rights. 110 | 111 | String[] 112 | 113 | String[] 114 | 115 | 116 | $env:computername 117 | 118 | 119 | LogFailures 120 | 121 | Create a log file of computers that failed. 122 | 123 | 124 | SwitchParameter 125 | 126 | 127 | False 128 | 129 | 130 | Credential 131 | 132 | Specify an alternate credential. 133 | 134 | PSCredential 135 | 136 | PSCredential 137 | 138 | 139 | None 140 | 141 | 142 | 143 | 144 | 145 | Computername 146 | 147 | The name of the computer to query. You must have admin rights. 148 | 149 | String[] 150 | 151 | String[] 152 | 153 | 154 | $env:computername 155 | 156 | 157 | LogFailures 158 | 159 | Create a log file of computers that failed. 160 | 161 | SwitchParameter 162 | 163 | SwitchParameter 164 | 165 | 166 | False 167 | 168 | 169 | Credential 170 | 171 | Specify an alternate credential. 172 | 173 | PSCredential 174 | 175 | PSCredential 176 | 177 | 178 | None 179 | 180 | 181 | 182 | 183 | 184 | 185 | Last Updated: 6 August, 2018 186 | 187 | 188 | 189 | 190 | -------------------------- EXAMPLE 1 -------------------------- 191 | PS C:\> Get-Info SRV1 192 | 193 | 194 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 195 | Version : 10.0.14393 196 | Uptime : 1.05:39:22.5945412 197 | MemoryGB : 1 198 | PhysicalProcessors : 1 199 | LogicalProcessors : 1 200 | ComputerName : SRV1 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Get-CimInstance 209 | 210 | 211 | 212 | Get-HWInfo 213 | 214 | 215 | 216 | Get-VolumeReport 217 | 218 | 219 | 220 | 221 | 222 | 223 | Get-VolumeReport 224 | Get 225 | VolumeReport 226 | 227 | Get volume report summary information. 228 | 229 | 230 | 231 | Use this command to get summary information about a storage volume on a remote server. 232 | 233 | 234 | 235 | Get-VolumeReport 236 | 237 | Cimsession 238 | 239 | A Cimsession object to a remote computer. 240 | 241 | CimSession[] 242 | 243 | CimSession[] 244 | 245 | 246 | None 247 | 248 | 249 | Drive 250 | 251 | Enter a drive letter like C or D without the colon. 252 | 253 | String 254 | 255 | String 256 | 257 | 258 | C 259 | 260 | 261 | 262 | Get-VolumeReport 263 | 264 | Computername 265 | 266 | Enter a computername 267 | 268 | String 269 | 270 | String 271 | 272 | 273 | Local host 274 | 275 | 276 | Drive 277 | 278 | Enter a drive letter like C or D without the colon. 279 | 280 | String 281 | 282 | String 283 | 284 | 285 | C 286 | 287 | 288 | 289 | 290 | 291 | Cimsession 292 | 293 | A Cimsession object to a remote computer. 294 | 295 | CimSession[] 296 | 297 | CimSession[] 298 | 299 | 300 | None 301 | 302 | 303 | Computername 304 | 305 | Enter a computername 306 | 307 | String 308 | 309 | String 310 | 311 | 312 | Local host 313 | 314 | 315 | Drive 316 | 317 | Enter a drive letter like C or D without the colon. 318 | 319 | String 320 | 321 | String 322 | 323 | 324 | C 325 | 326 | 327 | 328 | 329 | 330 | Microsoft.Management.Infrastructure.CimSession[] 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | System.String 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | System.Object 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | -------------------------- Example 1 -------------------------- 363 | PS C:\> Get-VolumeReport -computername SRV7 364 | 365 | Driveletter : C 366 | Size : 254721126400 367 | SizeRemaining : 11003076608 368 | HealthStatus : Healthy 369 | Date : 8/6/2018 12:10:10 PM 370 | Computername : SRV7 371 | 372 | Get volume summary information for drive C on SRV7. 373 | 374 | 375 | 376 | 377 | 378 | Get-HWInfo 379 | 380 | 381 | 382 | 383 | -------------------------------------------------------------------------------- /scripting/HelpDesk/functions.ps1: -------------------------------------------------------------------------------- 1 | 2 | Function Get-Info { 3 | 4 | [cmdletbinding()] 5 | 6 | Param ( 7 | [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] 8 | [Alias("cn")] 9 | [ValidateNotNullOrEmpty()] 10 | [string[]]$Computername = $env:computername, 11 | 12 | [pscredential]$Credential, 13 | 14 | [switch]$LogFailures 15 | ) 16 | 17 | Begin { 18 | Write-Verbose "[BEGIN] Starting $($MyInvocation.mycommand)" 19 | 20 | #define a hashtable of parameters to splat to 21 | 22 | #New-Cimsession 23 | $cimSess = @{ 24 | Erroraction = "Stop" 25 | SkipTestconnection = $True 26 | Computername = "" 27 | } 28 | 29 | if ($credential) { 30 | Write-Verbose "[BEGIN] Adding credential $($credential.username)" 31 | $cimSess.Add("Credential", $Credential) 32 | } 33 | if ($LogFailures) { 34 | 35 | #create a logfile name using the current date and time 36 | $logname = "{0}-InfoError.txt" -f (Get-Date -format yyyyMMddhhmm) 37 | #define the output path of the log file 38 | $logpath = Join-Path -Path $env:TEMP -ChildPath $logname 39 | 40 | Write-Verbose "[BEGIN] Errors will be logged to $Logpath" 41 | 42 | #define a header to add to the log file 43 | $msg = @" 44 | Execution Data 45 | Username : $env:USERDOMAIN\$env:username 46 | Computername : $env:computername 47 | PSVersion : $($PSVersionTable.psversion) 48 | Date : $(Get-Date) 49 | ScriptVersion : 1.1 50 | ************************************************** 51 | 52 | "@ 53 | $msg | Out-file -FilePath $logpath 54 | 55 | } #if logfailures 56 | 57 | } #begin 58 | 59 | Process { 60 | foreach ($computer in $computername) { 61 | #Get-CimInstance 62 | $cimParams = @{ 63 | Classname = "win32_Operatingsystem" 64 | Property = "caption", "csname", "lastbootuptime", "version", "totalvisiblememorysize" 65 | CimSession = "" 66 | } 67 | 68 | $cimSess.computername = $computer 69 | Try { 70 | Write-Verbose "[PROCESS] Creating CIMSession to $computer" 71 | $cimsess | Out-String | Write-Verbose 72 | $sess = New-CimSession @cimSess 73 | 74 | #add the session to the hashtable of parameters 75 | $cimParams.Cimsession = $sess 76 | 77 | Write-Verbose "[PROCESS] Processing $computer" 78 | $os = Get-CimInstance @cimparams 79 | 80 | Write-Verbose "[PROCESS] Getting Computersystem info" 81 | 82 | #define a hashtable of parameters to splat 83 | $cimparams.Classname = "win32_computersystem" 84 | $cimparams.property = 'NumberOfProcessors', 'NumberOfLogicalProcessors' 85 | 86 | $cs = Get-CimInstance @cimparams 87 | 88 | Write-Verbose "[PROCESS] Creating output object" 89 | $properties = [ordered]@{ 90 | Operatingsystem = $os.caption 91 | Version = $os.version 92 | Uptime = (Get-Date) - $os.lastbootuptime 93 | MemoryGB = $os.totalvisiblememorysize / 1MB -as [int32] 94 | PhysicalProcessors = $cs.NumberOfProcessors 95 | LogicalProcessors = $cs.NumberOfLogicalProcessors 96 | ComputerName = $os.CSName 97 | } 98 | 99 | #write the object to the pipeline 100 | New-Object -TypeName PSObject -Property $properties 101 | 102 | #clear the os variable for the next computer 103 | #shouldn't need to do this but just in case 104 | #something weird happens 105 | Remove-Variable OS, CS 106 | Write-Verbose "[PROCESS] Cleaning up CimSession" 107 | Remove-CimSession -cimsession $sess 108 | } #try 109 | Catch { 110 | Write-warning "Failed to contact $computer. $($_.exception.message)" 111 | If ($LogFailures) { 112 | #Write data to the log file 113 | "[$(Get-Date)] $($computer.toUpper())" | Out-File -FilePath $logpath -Append 114 | "[$(Get-Date)] $($_.exception.message)" | Out-File -FilePath $logpath -Append 115 | } #if logfailures 116 | if ($sess) { 117 | Remove-CimSession -CimSession $sess 118 | } 119 | } #Catch 120 | } #foreach 121 | } #process 122 | 123 | End { 124 | 125 | If ( $LogFailures -AND (Test-Path -Path $logpath)) { 126 | Write-Host "Errors were logged to $logpath" -ForegroundColor yellow 127 | } 128 | 129 | Write-verbose "[END] Exiting $($MyInvocation.MyCommand)" 130 | 131 | } #end 132 | 133 | } #close Get-Info 134 | 135 | Function Get-HWInfo { 136 | [cmdletbinding()] 137 | Param([string]$Computername = $env:computername) 138 | $data = Dofoo 139 | [pscustomobject]@{ 140 | Name = $computername.toUpper() 141 | Version = $data.version 142 | OS = "Windows Unicorn" 143 | FreeGB = $data.size 144 | } 145 | } 146 | 147 | Function Get-VolumeReport { 148 | [cmdletbinding(DefaultParameterSetName = "computer")] 149 | Param( 150 | [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = "session")] 151 | [ValidateNotNullorEmpty()] 152 | [Microsoft.Management.Infrastructure.CimSession[]]$Cimsession, 153 | [Parameter(Position = 0, ValueFromPipeline,ValueFromPipelineByPropertyName, 154 | HelpMessage = "Enter a computername", ParameterSetName = "computer")] 155 | [ValidateNotNullorEmpty()] 156 | [string]$Computername = $env:computername, 157 | [Parameter(HelpMessage = "Enter a drive letter like C or D without the colon.")] 158 | [ValidatePattern("[c-zC-Z")] 159 | [string]$Drive = "C" 160 | ) 161 | 162 | Begin { 163 | Write-Verbose "[BEGIN] Starting $($myinvocation.MyCommand)" 164 | } 165 | Process { 166 | if ($pscmdlet.ParameterSetName -eq "computer") { 167 | Write-Verbose "[PROCESS] Creating a temporary CimSession to $($Computername.toUpper())" 168 | Try { 169 | $Cimsession = New-CimSession -ComputerName $computername -ErrorAction Stop 170 | #set a flag to indicate this session was created here 171 | #so PowerShell can clean up 172 | $tempsession = $True 173 | } 174 | Catch { 175 | Write-Warning "Failed to create a CIMSession to $($Computername.toUpper()). $($_.exception.message)" 176 | #bail out 177 | return 178 | } 179 | } 180 | 181 | $params = @{ 182 | Erroraction = "Stop" 183 | driveletter = $Drive.toUpper() 184 | CimSession = $Cimsession 185 | } 186 | Write-Verbose "[PROCESS] Getting volume information for drive $Drive on $(($cimsession.computername).toUpper())" 187 | 188 | Get-Volume @params | 189 | Select-Object Driveletter, Size, SizeRemaining, HealthStatus, 190 | @{Name = "Date"; Expression = {(Get-Date)}}, 191 | @{Name = "Computername"; Expression = {$_.pscomputername.toUpper()}} 192 | 193 | if ($tempsession) { 194 | Write-Verbose "[PROCESS] Removing temporary CimSession" 195 | Remove-CimSession -CimSession $Cimsession 196 | } 197 | } #process 198 | End { 199 | Write-Verbose "[END] Ending $($myinvocation.MyCommand)" 200 | 201 | } 202 | } #close Get-VolumeReport 203 | 204 | # private helper function 205 | Function dofoo { 206 | [pscustomobject]@{ 207 | size = [math]::Round((Get-Random -Minimum 1gb -Maximum 10gb) / 1GB, 2) 208 | Version = "v{0}.0.0" -f (Get-Random -Minimum 2 -Maximum 6) 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /scripting/HelpDesk/vars.ps1: -------------------------------------------------------------------------------- 1 | #functions to export to user in this module 2 | 3 | $HelpDesk = "Please contact the HelpDesk at x1234" 4 | 5 | #import the list of computers, filtering out blanks and trimming spaces. 6 | $DomainComputers = (Get-Content $PSScriptRoot\computers.txt).where({$_ -match "\w+"}).foreach({$_.trim()}) 7 | -------------------------------------------------------------------------------- /scripting/MyInfo.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PSAutomationWorkshop/bd6e400b1dd8a8421ceca300096e5c4c6490c501/scripting/MyInfo.psd1 -------------------------------------------------------------------------------- /scripting/MyInfo.psm1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | #accept pipeline input and log failures 4 | 5 | Function Get-Info { 6 | 7 | <# 8 | .Synopsis 9 | Get help desk server information 10 | .Description 11 | Use this command to get basic server information. 12 | .Parameter Computername 13 | The name of the computer to query. You must have admin rights. 14 | .Example 15 | PS C:\> Get-Info SRV2 16 | 17 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 18 | Version : 10.0.14393 19 | Uptime : 91.05:39:22.5945412 20 | MemoryGB : 32 21 | PhysicalProcessors : 2 22 | LogicalProcessors : 8 23 | ComputerName : SRV2 24 | .Link 25 | Get-CimInstance 26 | .Notes 27 | Last Updated May 18, 2018 28 | version 1.1 29 | 30 | #> 31 | 32 | [cmdletbinding()] 33 | 34 | Param ( 35 | [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] 36 | [Alias("cn")] 37 | [ValidateNotNullOrEmpty()] 38 | [string[]]$Computername = $env:computername, 39 | 40 | [switch]$LogFailures 41 | ) 42 | 43 | Begin { 44 | Write-Verbose "[BEGIN] Starting $($MyInvocation.mycommand)" 45 | 46 | #define a hashtable of parameters to splat to 47 | #Get-CimInstance 48 | $cimParams = @{ 49 | Classname = "win32_Operatingsystem" 50 | ErrorAction = "Stop" 51 | Computername = "" 52 | } 53 | 54 | if ($LogFailures) { 55 | 56 | #create a logfile name using the current date and time 57 | $logname = "{0}-InfoError.txt" -f (Get-Date -format yyyyMMddhhmm) 58 | #define the output path of the log file 59 | $logpath = Join-Path -Path $env:TEMP -ChildPath $logname 60 | 61 | Write-Verbose "[BEGIN] Errors will be logged to $Logpath" 62 | 63 | #define a header to add to the log file 64 | $msg = @" 65 | Execution Data 66 | Username : $env:USERDOMAIN\$env:username 67 | Computername : $env:computername 68 | PSVersion : $($PSVersionTable.psversion) 69 | Date : $(Get-Date) 70 | ScriptVersion : 1.0 71 | ************************************************** 72 | 73 | "@ 74 | $msg | Out-file -FilePath $logpath 75 | 76 | } #if logfailures 77 | 78 | } #begin 79 | 80 | Process { 81 | foreach ($computer in $computername) { 82 | 83 | Write-Verbose "[PROCESS] Processing $computer" 84 | 85 | #add the currently process computer to the hashtable of parameters 86 | $cimParams.computername = $computer 87 | 88 | Try { 89 | $os = Get-CimInstance @cimparams 90 | 91 | if ($os) { 92 | Write-Verbose "[PROCESS] Getting Computersystem info" 93 | 94 | #define a hashtable of parameters to splat 95 | $csparams = @{ 96 | Classname = "win32_computersystem" 97 | computername = $os.csname 98 | Property = 'NumberOfProcessors', 'NumberOfLogicalProcessors' 99 | ErrorAction = 'Stop' 100 | } 101 | 102 | $cs = Get-CimInstance @csparams 103 | 104 | Write-Verbose "[PROCESS] Creating output object" 105 | $properties = [ordered]@{ 106 | Operatingsystem = $oS.caption 107 | Version = $os.version 108 | Uptime = (Get-Date) - $os.lastbootuptime 109 | MemoryGB = $os.totalvisiblememorysize / 1MB -as [int32] 110 | PhysicalProcessors = $cs.NumberOfProcessors 111 | LogicalProcessors = $cs.NumberOfLogicalProcessors 112 | ComputerName = $os.CSName 113 | } 114 | 115 | #write the object to the pipeline 116 | New-Object -TypeName PSObject -Property $properties 117 | 118 | #clear the os variable for the next computer 119 | #shouldn't need to do this but just in case 120 | #something weird happens 121 | Remove-Variable OS 122 | } #if 123 | 124 | } #try 125 | Catch { 126 | Write-warning "Failed to contact $computer" 127 | If ($LogFailures) { 128 | #Write data to the log file 129 | "[$(Get-Date)] $($computer.toUpper())" | Out-File -FilePath $logpath -Append 130 | "[$(Get-Date)] $($_.exception.message)" | Out-File -FilePath $logpath -Append 131 | } #if logfailures 132 | } #Catch 133 | } #foreach 134 | } #process 135 | 136 | End { 137 | 138 | If ( $LogFailures -AND (Test-Path -Path $logpath)) { 139 | Write-Host "Errors were logged to $logpath" -ForegroundColor yellow 140 | } 141 | 142 | Write-verbose "[END] Exiting $($MyInvocation.MyCommand)" 143 | } #end 144 | 145 | } #end Get-Info 146 | 147 | #define an alias 148 | Set-Alias -Name gin -Value get-info 149 | 150 | Export-ModuleMember -Function Get-Info -Alias gin -------------------------------------------------------------------------------- /scripting/blue.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | Body { 3 | font-family: "Tahoma", "Arial", "Helvetica", sans-serif; 4 | background-color:#F0E68C; 5 | } 6 | table 7 | { 8 | border-collapse:collapse; 9 | width:75% 10 | } 11 | td 12 | { 13 | font-size:12pt; 14 | border:1px #0000FF solid; 15 | padding:2px 2px 2px 2px; 16 | } 17 | th 18 | { 19 | font-size:14pt; 20 | text-align:center; 21 | padding-top:2px; 22 | padding-bottom:2px; 23 | padding-left:2px; 24 | padding-right:2px; 25 | background-color:#0000FF; 26 | color:#FFFFFF; 27 | } 28 | name tr 29 | { 30 | color:#000000; 31 | background-color:#0000FF; 32 | } 33 | h2 34 | { 35 | font-size:12pt; 36 | } -------------------------------------------------------------------------------- /scripting/demo-workflowparallel.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | Write-Warning "this is a walk-through demo" 4 | Return 5 | 6 | #disable my psdefaultparameter values to avoid conflicts 7 | $PSDefaultParameterValues["disabled"] = $True 8 | 9 | #demo Foreach Parallel 10 | 11 | #DemoForeachParallel "C:\work","C:\windows\SoftwareDistribution",$pshome 12 | 13 | Workflow DemoParallel { 14 | 15 | Write-Verbose -message "Starting parallel demo workflow" 16 | 17 | parallel { 18 | 19 | write-verbose -Message "Getting WMI data from $pscomputername" 20 | 21 | #one line commands 22 | #scope is super critical here if you want to access variables 23 | #in other scriptblocks in this workflow 24 | 25 | #these commands execute in parallel 26 | write-verbose -message "$((Get-Date).TimeOfDay) BIOS" 27 | $workflow:bios = Get-CimInstance -class win32_bios 28 | write-verbose -message "$((Get-Date).TimeOfDay) OS" 29 | $workflow:os = Get-CimInstance -class win32_operatingsystem 30 | write-verbose -message "$((Get-Date).TimeOfDay) Computersystem" 31 | $workflow:cs = Get-CimInstance -class win32_computersystem 32 | write-verbose -message "$((Get-Date).TimeOfDay) Disk" 33 | $workflow:disks = Get-CimInstance -Class win32_logicaldisk -filter "Drivetype=3" 34 | 35 | } #parallel 36 | 37 | $hash=[ordered]@{ 38 | Computername=$workflow:cs.Name 39 | Bios=$workflow:bios 40 | OperatingSystem=$workflow:os 41 | ComputerSystem=$workflow:cs 42 | Disks=$workflow:disks 43 | } 44 | 45 | #the output of new-Object must be assigned to a variable 46 | $obj = New-Object -TypeName PSObject -Property $hash 47 | 48 | $obj 49 | 50 | write-verbose -message "Finished with WMI data" 51 | 52 | } #close workflow 53 | 54 | demoparallel -pscomputername srv1,srv2,dom1 -verbose 55 | 56 | Workflow DemoForEachParallel { 57 | 58 | Param([string[]]$paths) 59 | 60 | Write-Verbose -message "Starting $WorkflowCommandName" 61 | 62 | $start=Get-Date 63 | 64 | foreach -parallel ($path in $paths) { 65 | 66 | if (Test-Path -path $path) { 67 | write-verbose -message $path 68 | $stat=dir -path $path -file -recurse | 69 | measure-object -Property Length -sum 70 | $obj=New-Object -TypeName PSObject -Property @{ 71 | Path=$Path 72 | TotalFiles=$stat.count 73 | TotalSizeMB=$stat.sum/1MB 74 | } 75 | $obj 76 | } 77 | else { 78 | Write-Verbose "Failed to find $path" 79 | } 80 | } #foreach 81 | 82 | $endtime = Get-Date 83 | 84 | Write-verbose -message ("Finished workflow in {0}" -f ($endtime-$start)) 85 | 86 | } #close workflow 87 | 88 | demoForEachparallel -paths c:\users,C:\windows\SoftwareDistribution,C:\windows\Temp -PSComputerName srv1,srv2,dom1 -verbose 89 | 90 | #region parallel demo 1 91 | Workflow New-Config { 92 | 93 | Parallel { 94 | Set-Service -ServiceName Browser -StartupType Manual 95 | Get-Process | 96 | Where-Object -filter {$_.ws -ge 50MB} | 97 | Export-Clixml -Path "C:\work\Proc50MB.xml" 98 | If (-Not (Test-Path "C:\Reports")) { 99 | New-Item -Path "C:\Reports" -ItemType Directory 100 | } 101 | } #end Parallel 102 | } #end workflow 103 | 104 | #endregion 105 | 106 | #region parallel demo 2 107 | Workflow New-Config2 { 108 | 109 | Parallel { 110 | Set-Service -ServiceName Browser -StartupType Manual 111 | Get-Process | Where-Object -filter {$_.ws -ge 50MB} | 112 | Export-Clixml -Path "C:\work\Proc50MB.xml" 113 | If (-Not (Test-Path "C:\Reports")) { 114 | New-Item -Path "C:\Reports" -ItemType Directory 115 | } 116 | Limit-EventLog -LogName System -MaximumSize 16MB 117 | } #end Parallel 118 | } #end workflow 119 | 120 | #endregion 121 | 122 | Workflow SequenceSample { 123 | 124 | Write-Verbose -message "Starting $workflowcommandname" 125 | Sequence { 126 | Write-Verbose -message "Sequence 1" 127 | 128 | $users = "adam","bob","charlie","david" 129 | foreach -parallel ($user in $users) { 130 | Write-Verbose -message "...Creating $user local account" 131 | start-sleep -Seconds (Get-Random -Minimum 1 -Maximum 5) 132 | } 133 | } 134 | Sequence { 135 | Write-Verbose -message "Sequence 2" 136 | Write-Verbose -message "...Doing something else" 137 | } 138 | Write-Verbose -message "Ending $workflowcommandname" 139 | } #end workflow 140 | 141 | Workflow ForEachParallelSample { 142 | foreach -parallel ($i in (1..100)) {$i*2} 143 | } #end workflow 144 | 145 | Workflow ForEachSample { 146 | foreach ($i in (1..100)) {$i*2; Start-Sleep -milliseconds 500 } 147 | } #end workflow 148 | 149 | Workflow DemoParallelSequence { 150 | 151 | "{0} Starting" -f (Get-date).TimeOfDay 152 | $a=10 153 | 154 | "`$a = $a" 155 | "`$b = $b" 156 | 157 | Parallel { 158 | Write-verbose -Message "In parallel" 159 | Sequence { 160 | "{0} sequence 1" -f (get-Date).TimeOfDay 161 | $workflow:a++ 162 | " `$a = $a" 163 | " `$b = $b" 164 | start-sleep -Seconds 1 165 | } 166 | Sequence { 167 | "{0} sequence 2" -f (get-Date).TimeOfDay 168 | $workflow:a++ 169 | $workflow:b=100 170 | " `$a = $a" 171 | " `$b = $b" 172 | start-sleep -Seconds 1 173 | } 174 | 175 | Sequence { 176 | "{0} sequence 3" -f (get-Date).TimeOfDay 177 | $workflow:a++ 178 | $workflow:b*=2 179 | " `$a = $a" 180 | " `$b = $b" 181 | start-sleep -Seconds 1 182 | } 183 | 184 | #this runs in parallel with the sequences 185 | "{0} Parallel" -f (Get-Date).TimeOfDay 186 | "`$a in parallel = $a" 187 | "`$b in parallel = $b" 188 | 189 | } #parallel 190 | 191 | #the results after Parallel 192 | "{0} Final Results" -f (get-date).TimeOfDay 193 | "`$a final = $a" 194 | "`$b final = $b" 195 | 196 | "{0} Ending" -f (get-date).TimeOfDay 197 | 198 | } #close workflow 199 | -------------------------------------------------------------------------------- /scripting/demo-workflows.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3.0 2 | 3 | Write-Warning "this is a walk-through demo" 4 | Return 5 | 6 | #disable my psdefaultparameter values to avoid conflicts 7 | $PSDefaultParameterValues["disabled"] = $True 8 | 9 | workflow Basic { 10 | 11 | Param() 12 | 13 | write-verbose -Message "Starting a basic workflow $($workflowcommandname)" 14 | 15 | $p = Get-Process -Name svchost 16 | # $p 17 | $p | Export-Clixml -path c:\svchosts.xml 18 | 19 | write-verbose -Message "Ending a basic workflow" 20 | 21 | } 22 | 23 | get-command -CommandType Workflow 24 | help basic 25 | #check dynamic parameters 26 | (Get-Command basic).Parameters 27 | 28 | Get-PSSessionConfiguration -Name *workflow* 29 | Basic -PSComputerName srv1,srv2 -PSCredential company\artd -verbose 30 | invoke-command { dir c:\svchosts.xml} -computername srv1,srv2 31 | 32 | Workflow ParamDemo { 33 | 34 | Param( 35 | [Parameter(Position=0,Mandatory,HelpMessage="Enter a path")] 36 | [ValidateNotNullorEmpty()] 37 | [string]$Path 38 | ) 39 | 40 | Write-Verbose -Message "Scanning $path" 41 | $start = Get-Date 42 | 43 | if (Test-Path -Path $path) { 44 | $stats = Get-ChildItem -Path $path -Recurse -File | 45 | Measure-Object -Property length -Sum 46 | 47 | <# 48 | $obj=New-Object -TypeName PSObject -Property @{ 49 | Path=$Path 50 | Size=$stats.sum 51 | Count=$stats.count 52 | } 53 | 54 | $obj 55 | #> 56 | 57 | #.NET Code used to not work 58 | [pscustomobject]@{ 59 | Path=$Path 60 | Size=$stats.sum 61 | Count=$stats.count 62 | } 63 | } 64 | else { 65 | Write-Error "Cannot find $path" 66 | } 67 | 68 | $endtime = Get-Date 69 | 70 | Write-verbose -Message "Finished workflow in $($endtime-$start)" 71 | 72 | } 73 | 74 | paramdemo -path c:\windows\softwaredistribution -pscomputername srv1,srv2 -verbose 75 | 76 | Workflow RestartMe { 77 | 78 | #this log should be created on $PSComputername 79 | $log="c:\wflog.txt" 80 | 81 | $t="{0} Starting {1}" -f (Get-date).TimeOfDay,$workflowcommandname 82 | 83 | $t | out-file -FilePath $log -encoding ascii 84 | 85 | #add some sample data to simulate doing something 86 | get-Process | 87 | Measure-Object -sum -maximum -average -property WS | 88 | Out-file -FilePath $log -append 89 | 90 | $t="{0} restarting" -f (Get-date).TimeOfDay 91 | $t | out-file -FilePath $log -append 92 | 93 | #this only works for remote computers 94 | Restart-Computer -force -Wait -for PowerShell 95 | 96 | $t="{0} Finished {1}" -f (Get-date).TimeOfDay,$workflowcommandname 97 | $t | Out-File -FilePath $log -Append 98 | 99 | } 100 | 101 | #demo InlineScript 102 | Workflow DemoInline { 103 | 104 | Write-verbose -Message "Starting the workflow" 105 | 106 | 5..1 | foreach-object { 107 | write-verbose -message $_ 108 | [math]::Pow($_,3) 109 | start-sleep 1 110 | } 111 | 112 | write-verbose -Message "Done" 113 | } 114 | 115 | Workflow DemoSequence { 116 | 117 | write-verbose -message ("{0} starting" -f (Get-Date).TimeofDay) 118 | $a=10 119 | $b=1 120 | 121 | "`$a = $a" 122 | "`$b = $b" 123 | "`$c = $c" 124 | 125 | Sequence { 126 | "{0} sequence 1" -f (Get-Date).TimeOfDay 127 | $workflow:a++ 128 | $c=1 129 | start-sleep -seconds 1 130 | } 131 | 132 | Sequence { 133 | "{0} sequence 2" -f (Get-Date).TimeofDay 134 | $workflow:a++ 135 | $workflow:b=100 136 | $c++ 137 | start-sleep -seconds 1 138 | } 139 | 140 | Sequence { 141 | "{0} sequence 3" -f (Get-Date).TimeofDay 142 | $workflow:a++ 143 | $workflow:b*=2 144 | $c++ 145 | start-sleep -seconds 1 146 | } 147 | "`$a = $a" 148 | "`$b = $b" 149 | "`$c = $c" 150 | 151 | write-verbose -Message ("{0} ending" -f (Get-Date).TimeOfDay) 152 | 153 | } 154 | 155 | 156 | Workflow ShowCommonParam { 157 | 158 | #demo running this from the console with tab completion 159 | 160 | $PSComputerName 161 | $PSCredential 162 | $PSConnectionRetryCount 163 | $PSActionRetryCount 164 | $PSPersist 165 | 166 | } 167 | 168 | Workflow DemoInlineScript { 169 | 170 | Write-verbose -Message "Starting the workflow" 171 | $a=1 172 | "`$a = $a" 173 | 174 | Inlinescript { 175 | 5..1 | foreach {$_ ; start-sleep 1} 176 | get-ciminstance -class "Win32_baseboard" 177 | $a++ 178 | } 179 | 180 | InlineScript { 181 | $p=get-process | sort-object -property WS -descending | 182 | Select-object -first 1 183 | $a++ 184 | } 185 | 186 | "`$a = $a" 187 | "Here is the top process:" 188 | $p 189 | write-verbose -Message "Done" 190 | } 191 | 192 | Workflow DemoNotUsing { 193 | 194 | Param([string]$log="System",[int]$newest=10) 195 | 196 | #creating a variable within the workflow 197 | $source="Service Control Manager" 198 | 199 | Write-verbose -message "Log parameter is $log" 200 | Write-Verbose -message "Source is $source" 201 | 202 | InlineScript { 203 | 204 | <# 205 | What happens when we try to access 206 | out of scope variables? 207 | #> 208 | "Getting newest {0} logs from {1} on {2}" -f $newest,$log,$pscomputername 209 | get-eventlog -LogName $log -Newest $newest -Source $source 210 | 211 | } #inlinescript 212 | 213 | Write-verbose -message "Ending workflow" 214 | 215 | } #close workflow 216 | 217 | Workflow DemoUsing { 218 | 219 | Param([string]$log="System",[int]$newest=10) 220 | 221 | #creating a variable within the workflow 222 | $source="Service Control Manager" 223 | 224 | Write-verbose -message "Log parameter is $log" 225 | Write-Verbose -message "Source is $source" 226 | 227 | InlineScript { 228 | 229 | <# 230 | this is the way to access out of scope variables. 231 | #> 232 | "Getting newest {0} logs from {1} on {2}" -f $using:newest,$using:log,$pscomputername 233 | get-eventlog -LogName $using:log -Newest $using:newest -Source $using:source 234 | 235 | } #inlinescript 236 | 237 | } #close workflow 238 | 239 | Workflow Test-NoPersistence { 240 | 241 | Write-Verbose -Message "Starting $workflowcommandname" 242 | <# 243 | run this workflow, then restart the computer 244 | when you see the countdown. When the computer 245 | comes back up run the workflow again. 246 | #> 247 | $p=get-process -Name lsass 248 | start-sleep -Seconds 5 249 | 250 | $n=(Get-Process).Count..0 251 | foreach ($i in $n) { 252 | $i 253 | (get-process)[$i] 254 | Start-Sleep -Seconds 1 255 | } #foreach 256 | 257 | $p | export-clixml -Path c:\lsass.xml 258 | Write-Verbose -Message "Finished $workflowcommandname" 259 | } 260 | 261 | Workflow Test-WFPersistence { 262 | 263 | Write-Verbose -Message "Starting $workflowcommandname" 264 | $p=get-process -Name lsass 265 | start-sleep -Seconds 5 266 | 267 | $n=(Get-Process).Count..0 268 | foreach ($i in $n) { 269 | $i 270 | (get-process)[$i] 271 | Start-Sleep -Seconds 1 272 | if ($i -eq 75) { 273 | Suspend-Workflow 274 | } 275 | } #foreach 276 | 277 | $p | export-clixml -Path c:\lsass.xml 278 | Write-Verbose -Message "Finished $workflowcommandname" 279 | } #test-wfpersistence 280 | 281 | <# 282 | this doesn't seem to really make much difference 283 | unless you plan on suspending the workflow. 284 | #> 285 | Workflow Test-PersistenceCheckpoint { 286 | 287 | Write-Verbose -Message "Starting $workflowcommandname" 288 | 289 | <# 290 | run this workflow, then restart the computer 291 | when you see the countdown. When the computer 292 | comes back up run the workflow again. 293 | #> 294 | $p=get-process -Name lsass 295 | start-sleep -Seconds 5 296 | Checkpoint-Workflow 297 | 298 | <# 299 | we can't use Checkpoint-Workflow within InlineScript 300 | so we'll have to restructure 301 | #> 302 | foreach ($i in (30..1)) { 303 | $i 304 | (get-process)[$i] 305 | Start-Sleep -Seconds 1 306 | #checkpoint on odd numbers 307 | if ($i%2) { 308 | Write-verbose -message "checkpoint" 309 | Checkpoint-Workflow 310 | } 311 | } #foreach 312 | 313 | Write-Verbose -message "Exporting process to xml" 314 | $p | export-clixml -Path c:\lsass.xml 315 | 316 | Write-Verbose -Message "Finished $workflowcommandname" 317 | } 318 | 319 | Workflow Test-Suspend { 320 | 321 | "{0} Starting $WorkflowCommandName on {1}" -f (Get-Date).TimeOfDay 322 | 323 | $start=Get-Date 324 | $p=get-process -Name lsass 325 | 326 | start-sleep -Seconds 5 327 | $a=123 328 | "a originally = $a" 329 | 330 | #suspending will automatically create a job 331 | Suspend-workflow 332 | 333 | <# 334 | look at jobs 335 | exit PowerShell sessions 336 | open a new session 337 | Run Get-Job 338 | Import-module PSWorkflow 339 | Run Get-Job 340 | Resume job 341 | receive job results 342 | #> 343 | 344 | #we'll only see results from this point on when you resume the job 345 | $a*=2 346 | 347 | "a is finally $a" 348 | 349 | InlineScript { $using:p} 350 | 351 | $endtime=Get-Date 352 | 353 | "Total elapsed time = {0}" -f ($endtime-$start) 354 | 355 | "{0} Ending $workflowcommandname on {1}" -f (Get-Date).TimeOfDay,$($pscomputername) 356 | 357 | } 358 | 359 | 360 | Workflow Test-Persistence2 { 361 | 362 | <# 363 | 1. Run with no parameters then reboot after the countdown has begun. 364 | Open PowerShell and check for workflows or jobs. Don't forget to 365 | import the PSWorkflow module. If there is a job, resume it and get 366 | job results. What did you get? 367 | 368 | 2. Run with workflow with -pspersist $True and repeat. 369 | 370 | 3. Run the workflow -asJob. 371 | Run Get-Job 372 | Suspend the job with Suspend-Job 373 | Exit Powershell and start a new session or reboot. 374 | Import the PSWorkflow module 375 | Resume the job 376 | Get results. Did you use -PSPersist $True? What did you get? 377 | #> 378 | 379 | "{0} Starting $workflowcommandname" -f (Get-Date).TimeOfDay 380 | 381 | $start=Get-Date 382 | 383 | $a=0 384 | 385 | Do { 386 | $a 387 | #get a random process to simulate some activity 388 | get-process | get-random 389 | $a++ 390 | Start-Sleep -milliseconds 500 391 | } until ($a -gt 100) 392 | 393 | $endtime=Get-Date 394 | 395 | "Total elapsed time = {0}" -f ($endtime-$start) 396 | 397 | "{0} Ending $WorkflowCommandName" -f (Get-Date).TimeOfDay 398 | 399 | } 400 | 401 | 402 | Workflow Test-SuspendMe { 403 | 404 | #this log should be created on $PSComputername 405 | $log="c:\wflog.txt" 406 | 407 | $s=Get-Service -name w* 408 | 409 | $t="{0} Starting" -f (Get-date).TimeOfDay,$workflowcommandname 410 | 411 | $t | out-file -FilePath $log 412 | 413 | #add some sample data to simulate doing something 414 | get-Process | 415 | Measure-Object -sum -maximum -average -property WS | 416 | Out-file -FilePath $log -append 417 | 418 | $t="{0} restarting" -f (Get-date).TimeOfDay 419 | $t | out-file -FilePath $log -append 420 | 421 | #suspending automatically creates a checkpoint 422 | Suspend-workflow 423 | 424 | $s | Out-File -FilePath $log -Append 425 | $t="{0} Finished" -f (Get-date).TimeOfDay,$workflowcommandname 426 | $t | Out-File -FilePath $log -Append 427 | 428 | } 429 | 430 | Workflow Test-SuspendOnError { 431 | 432 | Write-Verbose -Message "Starting $workflowcommandname" 433 | 434 | <# 435 | run this workflow, then restart the computer 436 | when you see the countdown. When an error is 437 | detected, suspend the workflow which should automatically 438 | checkpoint the workflow and create a suspended job. 439 | 440 | What happens when you resume the job? Do you get results? 441 | #> 442 | 443 | $n=(get-Process).count..0 444 | $p=get-process -Name lsass 445 | start-sleep -Seconds 5 446 | 447 | foreach ($i in $n) { 448 | Try { 449 | $i 450 | (get-process -erroraction Stop)[$i] | export-clixml -path "C:\process-$i.xml" 451 | Start-Sleep -Seconds 1 452 | } 453 | Catch { 454 | Write-Warning -message "An error was detected. Suspending the workflow." 455 | #Suspend-Workflow automatically creates a checkpoint 456 | Suspend-Workflow 457 | 458 | } #Catch 459 | 460 | } #foreach 461 | 462 | Write-Verbose -message "Getting variables" 463 | $p 464 | $n.count 465 | 466 | Write-Verbose -Message "Finished $workflowcommandname" 467 | 468 | } #end Test-SuspendOnError 469 | 470 | 471 | Workflow Test-SuspendOnError2 { 472 | 473 | Write-Verbose -Message "Starting $workflowcommandname" 474 | 475 | <# 476 | run this workflow, then restart the computer 477 | when you see the countdown. When an error is 478 | detected, suspend the workflow which should automatically 479 | checkpoint the workflow and create a suspended job. 480 | 481 | What happens when you resume the job? Do you get results? 482 | #> 483 | 484 | $n=(get-Process).count..0 485 | $p=get-process -Name lsass 486 | Write-Verbose -message "Initial checkpoint" 487 | Checkpoint-Workflow 488 | start-sleep -Seconds 5 489 | 490 | foreach ($i in $n) { 491 | Try { 492 | $i 493 | (get-process -erroraction Stop)[$i] | export-clixml -path "C:\process-$i.xml" 494 | Start-Sleep -Seconds 1 495 | } 496 | Catch { 497 | Write-Warning -message "An error was detected. Suspending the workflow." 498 | Suspend-Workflow 499 | 500 | } #Catch 501 | 502 | #checkpoint on odd numbers 503 | if ($i%2) { 504 | Write-verbose -message "Checkpoint $i" 505 | Checkpoint-Workflow 506 | } 507 | 508 | } #foreach 509 | 510 | Write-Verbose -message "Getting variables" 511 | $p 512 | $n.count 513 | Write-Verbose -Message "Finished $workflowcommandname" 514 | 515 | } #end Test-SuspendOnError2 516 | 517 | -------------------------------------------------------------------------------- /scripting/info2.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | # get server information for the help desk 4 | 5 | Param ( 6 | [alias("cow","cn")] 7 | [string]$Computername 8 | ) 9 | 10 | Set-StrictMode -Version 2.0 11 | 12 | <# 13 | Get computer information with Get-CimInstance (WMI) and 14 | create a custom object 15 | #> 16 | 17 | write-host "Getting server information for $Computername" -ForegroundColor Cyan 18 | 19 | Get-CimInstance -ClassName win32_operatingsystem -ComputerName $computername | 20 | Select-Object -Property Caption, Version, 21 | @{Name = "Uptime"; Expression = {(Get-Date) - $_.lastbootuptime}}, 22 | @{Name = "MemoryGB"; Expression = {$_.totalvisiblememorysize / 1MB -as [int32]}}, 23 | @{Name = "PhysicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $computername -Property NumberOfProcessors).NumberOfProcessors }}, 24 | @{Name = "LogicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $computername -property NumberOfLogicalProcessors).NumberOfLogicalProcessors }}, 25 | @{Name = "ComputerName"; Expression = {$_.CSName}} 26 | 27 | -------------------------------------------------------------------------------- /scripting/info2a.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | # get server information for the help desk 4 | 5 | Function Get-Cow { 6 | 7 | [cmdletbinding()] 8 | 9 | Param ( 10 | [Parameter(Mandatory, HelpMessage = "Enter the name of a cow." )] 11 | [alias("cow","cn")] 12 | [ValidateNotNullOrEmpty()] 13 | [ValidateSet("think51","foo","bar")] 14 | [ValidateScript( { Test-Connection -ComputerName $_ -Count 1 -Quiet })] 15 | [string]$Computername 16 | ) 17 | 18 | Set-StrictMode -Version 2.0 19 | 20 | <# 21 | Get computer information with Get-CimInstance (WMI) and 22 | create a custom object 23 | #> 24 | 25 | Write-Verbose "[$(Get-date)] Starting Get-Cow" 26 | 27 | write-Host "Getting server information for $Computername" -ForegroundColor Cyan 28 | 29 | Write-Verbose "[$(Get-Date) Querying $computername" 30 | 31 | Get-CimInstance -ClassName win32_operatingsystem -ComputerName $computername | 32 | Select-Object -Property Caption, Version, 33 | @{Name = "Uptime"; Expression = {(Get-Date) - $_.lastbootuptime}}, 34 | @{Name = "MemoryGB"; Expression = {$_.totalvisiblememorysize / 1MB -as [int32]}}, 35 | @{Name = "PhysicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $computername -Property NumberOfProcessors).NumberOfProcessors }}, 36 | @{Name = "LogicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $computername -property NumberOfLogicalProcessors).NumberOfLogicalProcessors }}, 37 | @{Name = "ComputerName"; Expression = {$_.CSName}} 38 | 39 | Write-Verbose "[$(Get-date)] Ending Get-Cow" 40 | 41 | } #end Get-Cow function -------------------------------------------------------------------------------- /scripting/info3.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | <# 4 | an improved version that supports multiple computer names 5 | and simplifies the Get-CimInstance queries 6 | #> 7 | 8 | Function Get-ServerInfo { 9 | [cmdletbinding()] 10 | Param ( 11 | [Parameter(Mandatory,HelpMessage = "Enter the name of a cow" )] 12 | [alias("cow","cn")] 13 | [ValidateNotNullOrEmpty()] 14 | [string[]]$Computername 15 | ) 16 | 17 | Write-Verbose "[$(Get-date)] Starting Get-ServerInfo" 18 | 19 | foreach ($computer in $computername) { 20 | 21 | Write-Verbose "[$(Get-Date)] Querying $Computer" 22 | #for the sake of the demo I am suppressing verbose output 23 | #from Get-CimInstance 24 | $cs = Get-CimInstance -ClassName win32_operatingsystem -ComputerName $computer -verbose:$false 25 | 26 | if ($cs) { 27 | Write-Verbose "[$(Get-Date)] Getting details" 28 | 29 | $cs | select-Object -Property Caption, Version, 30 | @{Name = "Uptime"; Expression = {(Get-Date) - $_.lastbootuptime}}, 31 | @{Name = "MemoryGB"; Expression = {$_.totalvisiblememorysize / 1MB -as [int32]}}, 32 | @{Name = "PhysicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $_.csname -Property NumberOfProcessors -verbose:$false).NumberOfProcessors }}, 33 | @{Name = "LogicalProcessors"; Expression = { (Get-CimInstance win32_computersystem -ComputerName $_.csname -property NumberOfLogicalProcessors -verbose:$false).NumberOfLogicalProcessors }}, 34 | @{Name = "ComputerName"; Expression = {$_.CSName}} 35 | } #if 36 | 37 | } #foreach 38 | 39 | Write-Verbose "[$(Get-date)] Ending Get-ServerInfo" 40 | 41 | } #end Get-ServerInfo function 42 | 43 | -------------------------------------------------------------------------------- /scripting/info4.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | Function Get-ServerInfo { 4 | 5 | [cmdletbinding()] 6 | 7 | Param ( 8 | [Parameter(Position = 0)] 9 | [Alias("cn")] 10 | [ValidateNotNullOrEmpty()] 11 | [string[]]$Computername = $env:computername 12 | ) 13 | 14 | Write-Verbose "[BEGIN] Starting $($myinvocation.MyCommand)" 15 | 16 | #loop through each computername that is part of $Computername 17 | foreach ($computer in $computername) { 18 | 19 | Write-verbose "[PROCESS] Processing $computer" 20 | 21 | Try { 22 | #get Operating system information from WMI on each computer 23 | $os = Get-CimInstance -ClassName win32_operatingsystem -ComputerName $computer -ErrorAction stop 24 | 25 | #moved this to the Try block so if there 26 | #is an error querying win32_computersystem, the same 27 | #catch block will be used 28 | if ($os) { 29 | Write-Verbose "[PROCESS] Getting Computersystem info" 30 | 31 | #get computer system information from WMI on each computer 32 | $cs = Get-CimInstance win32_computersystem -ComputerName $os.csname -Property NumberOfProcessors,NumberOfLogicalProcessors -ErrorAction Stop 33 | 34 | #create an ordered hashtable that will be turned into an object 35 | $properties = [ordered]@{ 36 | Operatingsystem = $os.caption 37 | Version = $os.version 38 | Uptime = (Get-Date) - $os.lastbootuptime 39 | MemoryGB = $os.totalvisiblememorysize / 1MB -as [int32] 40 | PhysicalProcessors = $cs.NumberOfProcessors 41 | LogicalProcessors = $cs.NumberOfLogicalProcessors 42 | ComputerName = $os.CSName 43 | } 44 | 45 | #create a custom object using the hashtable as properties and values 46 | New-Object -TypeName PSObject -Property $properties 47 | 48 | } #if $OS has a value 49 | 50 | } #try 51 | 52 | Catch { 53 | Write-Warning "Failed to contact $($computer.ToUpper())" 54 | Write-Warning $_.exception.message 55 | } #catch 56 | 57 | } #foreach 58 | 59 | Write-verbose "[END] Exiting $($myinvocation.MyCommand)" 60 | 61 | } #end Get-ServerInfo 62 | 63 | -------------------------------------------------------------------------------- /scripting/info5.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | #this version includes a timout parameter 4 | #comment based help and splatting 5 | 6 | Function Get-ServerInfo { 7 | 8 | <# 9 | .Synopsis 10 | Get help desk server information 11 | 12 | .Description 13 | Use this command to get basic server information. 14 | 15 | .Parameter Computername 16 | The name of the computer to query. You must have admin rights. 17 | 18 | .Parameter Timeout 19 | Enter the number of seconds between 1 and 10 to wait for a WMI connection. 20 | 21 | .Example 22 | PS C:\> Get-ServerInfo SRV1 23 | 24 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 25 | Version : 10.0.14393 26 | Uptime : 1.05:39:22.5945412 27 | MemoryGB : 4 28 | PhysicalProcessors : 2 29 | LogicalProcessors : 4 30 | ComputerName : SRV1 31 | 32 | Get server configuration data from SRV1. 33 | 34 | .Example 35 | PS C:\> Get-ServerInfo SRV1,SRV2 | Export-CSV -path c:\reports\data.csv -append 36 | 37 | Get server info and append to a CSV file. 38 | 39 | .Link 40 | Get-CimInstance 41 | 42 | .Link 43 | http://google.com 44 | 45 | .Notes 46 | Last Updated May 18, 2018 47 | version 1.1 48 | 49 | #> 50 | 51 | [cmdletbinding()] 52 | 53 | Param ( 54 | [Parameter(Position = 0)] 55 | [Alias("cow", "cn")] 56 | [ValidateNotNullOrEmpty()] 57 | [string[]]$Computername = $env:computername, 58 | 59 | [ValidateRange(1,10)] 60 | [int32]$Timeout 61 | ) 62 | 63 | Write-Verbose "[BEGIN] Starting $($MyInvocation.MyCommand)" 64 | 65 | #define a hashtable of parameter values to splat to Get-CimInstance 66 | $cimParams = @{ 67 | Classname = "win32_Operatingsystem" 68 | ErrorAction = "Stop" 69 | Computername = "" 70 | } 71 | 72 | if ($timeout) { 73 | Write-Verbose "[BEGIN] Adding timeout value of $timeout" 74 | $cimParams.add("OperationTimeOutSec", $Timeout) 75 | } 76 | 77 | foreach ($computer in $computername) { 78 | 79 | Write-Verbose "[PROCESS] Processing computer: $($computer.toUpper())" 80 | 81 | $cimParams.computername = $computer 82 | 83 | Try { 84 | $os = Get-CimInstance @cimparams 85 | 86 | #moved this to the Try block so if there 87 | #is an error querying win32_computersystem, the same 88 | #catch block will be used 89 | if ($os) { 90 | Write-Verbose "[PROCESS] Getting Computersystem info" 91 | 92 | $csparams = @{ 93 | Classname = "win32_computersystem" 94 | computername = $os.csname 95 | Property = 'NumberOfProcessors', 'NumberOfLogicalProcessors' 96 | ErrorAction = 'Stop' 97 | } 98 | 99 | if ($timeout) { 100 | $csParams.add("OperationTimeOutSec", $Timeout) 101 | } 102 | 103 | $cs = Get-CimInstance @csparams 104 | 105 | Write-Verbose "[PROCESS] Creating output object" 106 | $properties = [ordered]@{ 107 | Operatingsystem = $os.caption 108 | Version = $os.version 109 | Uptime = (Get-Date) - $os.lastbootuptime 110 | MemoryGB = $os.totalvisiblememorysize / 1MB -as [int32] 111 | PhysicalProcessors = $cs.NumberOfProcessors 112 | LogicalProcessors = $cs.NumberOfLogicalProcessors 113 | ComputerName = $os.CSName 114 | } 115 | 116 | New-Object -TypeName PSObject -Property $properties 117 | 118 | Remove-Variable os 119 | } #if 120 | } 121 | Catch { 122 | #variation on warning message 123 | $msg = "Failed to contact $($computer.ToUpper()). $($_.exception.message)" 124 | 125 | Write-Warning $msg 126 | 127 | } 128 | 129 | } #foreach 130 | 131 | Write-verbose "[END] Exiting $($MyInvocation.MyCommand)" 132 | 133 | } #end Get-ServerInfo 134 | 135 | -------------------------------------------------------------------------------- /scripting/info6.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 4.0 2 | 3 | #accept pipeline input and log failures 4 | 5 | Function Get-Info { 6 | 7 | <# 8 | .Synopsis 9 | Get help desk server information 10 | .Description 11 | Use this command to get basic server information. 12 | .Parameter Computername 13 | The name of the computer to query. You must have admin rights. 14 | .Example 15 | PS C:\> Get-Info SRV2 16 | 17 | Operatingsystem : Microsoft Windows Server 2016 Standard Evaluation 18 | Version : 10.0.14393 19 | Uptime : 91.05:39:22.5945412 20 | MemoryGB : 32 21 | PhysicalProcessors : 2 22 | LogicalProcessors : 8 23 | ComputerName : SRV2 24 | .Link 25 | Get-CimInstance 26 | .Notes 27 | Last Updated May 18, 2018 28 | version 1.1 29 | 30 | #> 31 | 32 | [cmdletbinding()] 33 | 34 | Param ( 35 | [Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] 36 | [Alias("cn")] 37 | [ValidateNotNullOrEmpty()] 38 | [string[]]$Computername = $env:computername, 39 | 40 | [pscredential]$Credential, 41 | 42 | [switch]$LogFailures 43 | ) 44 | 45 | Begin { 46 | Write-Verbose "[BEGIN] Starting $($MyInvocation.mycommand)" 47 | 48 | 49 | #define a hashtable of parameters to splat to 50 | 51 | #New-Cimsession 52 | $cimSess = @{ 53 | Erroraction = "Stop" 54 | SkipTestconnection = $True 55 | Computername = "" 56 | } 57 | 58 | if ($credential) { 59 | Write-Verbose "[BEGIN] Adding credential $($credential.username)" 60 | $cimSess.Add("Credential",$Credential) 61 | } 62 | 63 | 64 | if ($LogFailures) { 65 | 66 | #create a logfile name using the current date and time 67 | $logname = "{0}-InfoError.txt" -f (Get-Date -format yyyyMMddhhmm) 68 | #define the output path of the log file 69 | $logpath = Join-Path -Path $env:TEMP -ChildPath $logname 70 | 71 | Write-Verbose "[BEGIN] Errors will be logged to $Logpath" 72 | 73 | #define a header to add to the log file 74 | $msg = @" 75 | Execution Data 76 | Username : $env:USERDOMAIN\$env:username 77 | Computername : $env:computername 78 | PSVersion : $($PSVersionTable.psversion) 79 | Date : $(Get-Date) 80 | ScriptVersion : 1.1 81 | ************************************************** 82 | 83 | "@ 84 | $msg | Out-file -FilePath $logpath 85 | 86 | } #if logfailures 87 | 88 | } #begin 89 | 90 | Process { 91 | foreach ($computer in $computername) { 92 | #Get-CimInstance 93 | $cimParams = @{ 94 | Classname = "win32_Operatingsystem" 95 | Property = "caption","csname","lastbootuptime","version","totalvisiblememorysize" 96 | CimSession = "" 97 | } 98 | 99 | $cimSess.computername = $computer 100 | Try { 101 | Write-Verbose "[PROCESS] Creating CIMSession to $computer" 102 | $cimsess | Out-String | Write-Verbose 103 | $sess = New-CimSession @cimSess 104 | 105 | #add the session to the hashtable of parameters 106 | $cimParams.Cimsession = $sess 107 | 108 | Write-Verbose "[PROCESS] Processing $computer" 109 | $os = Get-CimInstance @cimparams 110 | 111 | Write-Verbose "[PROCESS] Getting Computersystem info" 112 | 113 | #define a hashtable of parameters to splat 114 | $cimparams.Classname = "win32_computersystem" 115 | $cimparams.property ='NumberOfProcessors', 'NumberOfLogicalProcessors' 116 | 117 | $cs = Get-CimInstance @cimparams 118 | 119 | Write-Verbose "[PROCESS] Creating output object" 120 | $properties = [ordered]@{ 121 | Operatingsystem = $os.caption 122 | Version = $os.version 123 | Uptime = (Get-Date) - $os.lastbootuptime 124 | MemoryGB = $os.totalvisiblememorysize / 1MB -as [int32] 125 | PhysicalProcessors = $cs.NumberOfProcessors 126 | LogicalProcessors = $cs.NumberOfLogicalProcessors 127 | ComputerName = $os.CSName 128 | } 129 | 130 | #write the object to the pipeline 131 | New-Object -TypeName PSObject -Property $properties 132 | 133 | #clear the os variable for the next computer 134 | #shouldn't need to do this but just in case 135 | #something weird happens 136 | Remove-Variable OS,CS 137 | Write-Verbose "[PROCESS] Cleaning up CimSession" 138 | Remove-CimSession -cimsession $sess 139 | } #try 140 | Catch { 141 | Write-warning "Failed to contact $computer. $($_.exception.message)" 142 | If ($LogFailures) { 143 | #Write data to the log file 144 | "[$(Get-Date)] $($computer.toUpper())" | Out-File -FilePath $logpath -Append 145 | "[$(Get-Date)] $($_.exception.message)" | Out-File -FilePath $logpath -Append 146 | } #if logfailures 147 | if ($sess) { 148 | Remove-CimSession -CimSession $sess 149 | } 150 | } #Catch 151 | } #foreach 152 | } #process 153 | 154 | End { 155 | 156 | If ( $LogFailures -AND (Test-Path -Path $logpath)) { 157 | Write-Host "Errors were logged to $logpath" -ForegroundColor yellow 158 | } 159 | 160 | Write-verbose "[END] Exiting $($MyInvocation.MyCommand)" 161 | 162 | } #end 163 | 164 | } #close Get-Info 165 | -------------------------------------------------------------------------------- /scripting/infoscript.ps1: -------------------------------------------------------------------------------- 1 | #this is the PowerShell code that works interactively in the console 2 | 3 | $computername = Read-Host "Enter a computername" 4 | 5 | gcim win32_operatingsystem -comp $computername | 6 | Select Caption,Version, 7 | @{Name="Uptime";Expression={(Get-Date) - $_.lastbootuptime}}, 8 | @{Name="MemoryGB";Expression={$_.totalvisiblememorysize/1MB -as [int32]}}, 9 | @{Name="PhysicalProcessors";Expression = { (gcim win32_computersystem -ComputerName $computername -Property NumberOfProcessors).NumberOfProcessors }}, 10 | @{Name="LogicalProcessors";Expression = { (gcim win32_computersystem -ComputerName $computername -property NumberOfLogicalProcessors).NumberOfLogicalProcessors }}, 11 | @{Name="ComputerName";Expression={$_.CSName}} 12 | -------------------------------------------------------------------------------- /scripting/myTemplates/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // When enabled, will trim trailing whitespace when you save a file. 3 | "files.trimTrailingWhitespace": true 4 | } 5 | -------------------------------------------------------------------------------- /scripting/myTemplates/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${relativeFile}: the current opened file relative to workspaceRoot 5 | // ${fileBasename}: the current opened file's basename 6 | // ${fileDirname}: the current opened file's dirname 7 | // ${fileExtname}: the current opened file's extension 8 | // ${cwd}: the current working directory of the spawned process 9 | { 10 | // See https://go.microsoft.com/fwlink/?LinkId=733558 11 | // for the documentation about the tasks.json format 12 | "version": "2.0.0", 13 | 14 | // Start PowerShell 15 | "windows": { 16 | "command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe", 17 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] 18 | }, 19 | "powershellcore-windows" : { 20 | "command" :"${env:programfiles}/PowerShell/*/pwsh.exe", 21 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] 22 | 23 | }, 24 | "powershellcore-linux": { 25 | "command": "/usr/bin/pwsh", 26 | "args": [ "-NoProfile" ] 27 | }, 28 | "powershellcore-osx": { 29 | "command": "/usr/local/bin/pwsh", 30 | "args": [ "-NoProfile" ] 31 | }, 32 | 33 | // Associate with test task runner 34 | "tasks": [ 35 | { 36 | "taskName": "Test Plaster Manifest", 37 | "suppressTaskName": true, 38 | "isTestCommand": true, 39 | "args": [ 40 | "Write-Host 'Invoking Pester...'; $ProgressPreference = 'SilentlyContinue'; Invoke-Pester -Script ./tests/myproject.tests.ps1 -PesterOption @{IncludeVSCodeMarker=$true};", 41 | "Invoke-Command { Write-Host 'Completed Test task in task runner.' }" 42 | ], 43 | "problemMatcher": "$pester" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /scripting/myTemplates/myFunction/function-template.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 5.0 2 | 3 | <% 4 | @" 5 | # version: $PLASTER_PARAM_version 6 | # created: $PLASTER_Date 7 | "@ 8 | %> 9 | 10 | <% 11 | "Function $PLASTER_PARAM_Name {" 12 | %> 13 | <% 14 | If ($PLASTER_PARAM_Help -eq 'Yes') 15 | { 16 | @" 17 | <# 18 | .SYNOPSIS 19 | Short description 20 | .DESCRIPTION 21 | Long description 22 | .PARAMETER XXX 23 | Describe the parameter 24 | .EXAMPLE 25 | Example of how to use this cmdlet 26 | .NOTES 27 | insert any notes 28 | .LINK 29 | insert links 30 | #> 31 | "@ 32 | } 33 | %> 34 | <% 35 | if ($PLASTER_PARAM_ShouldProcess -eq 'Yes') { 36 | "[cmdletbinding(SupportsShouldProcess)]" 37 | } 38 | else { 39 | "[cmdletbinding()]" 40 | } 41 | %> 42 | <% 43 | "[OutputType($PLASTER_PARAM_OutputType)]" 44 | %> 45 | 46 | <% 47 | if ($PLASTER_PARAM_computername -eq 'Yes') { 48 | @' 49 | Param( 50 | [Parameter(Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)] 51 | [ValidateNotNullorEmpty()] 52 | [string[]]$ComputerName = $env:COMPUTERNAME 53 | ) 54 | '@ 55 | } 56 | else { 57 | @' 58 | Param() 59 | '@ 60 | } 61 | %> 62 | 63 | Begin { 64 | Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)" 65 | 66 | } #begin 67 | 68 | Process { 69 | <% 70 | if ($PLASTER_PARAM_computername -eq 'Yes') { 71 | @' 72 | Foreach ($computer in $Computername) { 73 | Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $($computer.toUpper())" 74 | # 75 | } 76 | '@ 77 | } 78 | else { 79 | 'Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing"' 80 | } 81 | %> 82 | } #process 83 | 84 | End { 85 | Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)" 86 | } #end 87 | 88 | <% 89 | "} #close $PLASTER_PARAM_Name " 90 | %> 91 | -------------------------------------------------------------------------------- /scripting/myTemplates/myFunction/plasterManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | myFunction 7 | 426daaff-5080-4e71-81d3-756c5afc9f43 8 | 1.0.0 9 | myFunction 10 | Function scaffolding 11 | Art Deco 12 | function 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | /|= Scaffolding your PowerShell function $PLASTER_PARAM_Name =|\ 35 | 36 | 37 | 38 | 39 | Your function, '$PLASTER_PARAM_Name', has been saved to '$PLASTER_DestinationPath\$PLASTER_PARAM_Name.ps1' 40 | 41 | -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/License.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PSAutomationWorkshop/bd6e400b1dd8a8421ceca300096e5c4c6490c501/scripting/myTemplates/myProject/License.txt -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/README.md: -------------------------------------------------------------------------------- 1 | # <%=$PLASTER_PARAM_ModuleName%> 2 | 3 | 4 | *last updated <%=$PLASTER_Date%>* -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/author-note.txt: -------------------------------------------------------------------------------- 1 | This version of the Plaster framework includes template files -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/changelog.txt: -------------------------------------------------------------------------------- 1 | #Changelog for <%=$PLASTER_PARAM_ModuleName%> 2 | 3 | -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/editor/VSCode/settings.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | // When enabled, will trim trailing whitespace when you save a file. 4 | "files.trimTrailingWhitespace": true 5 | } 6 | -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/editor/VSCode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${relativeFile}: the current opened file relative to workspaceRoot 5 | // ${fileBasename}: the current opened file's basename 6 | // ${fileDirname}: the current opened file's dirname 7 | // ${fileExtname}: the current opened file's extension 8 | // ${cwd}: the current working directory of the spawned process 9 | { 10 | // See https://go.microsoft.com/fwlink/?LinkId=733558 11 | // for the documentation about the tasks.json format 12 | "version": "2.0.0", 13 | 14 | // Start PowerShell 15 | "windows": { 16 | "command": "${env:windir}/System32/WindowsPowerShell/v1.0/powershell.exe", 17 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] 18 | }, 19 | "powershellcore-windows" : { 20 | "command" :"${env:programfiles}/PowerShell/*/pwsh.exe", 21 | "args": [ "-NoProfile", "-ExecutionPolicy", "Bypass" ] 22 | 23 | }, 24 | "powershellcore-linux": { 25 | "command": "/usr/bin/pwsh", 26 | "args": [ "-NoProfile" ] 27 | }, 28 | "powershellcore-osx": { 29 | "command": "/usr/local/bin/pwsh", 30 | "args": [ "-NoProfile" ] 31 | }, 32 | 33 | // Associate with test task runner 34 | "tasks": [ 35 | { 36 | "taskName": "Test", 37 | "suppressTaskName": true, 38 | "isTestCommand": true, 39 | "args": [ 40 | "Write-Host 'Invoking Pester...'; $ProgressPreference = 'SilentlyContinue'; Invoke-Pester -Script test -PesterOption @{IncludeVSCodeMarker=$true};", 41 | "Invoke-Command { Write-Host 'Completed Test task in task runner.' }" 42 | ], 43 | "problemMatcher": "$pester" 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/module.psm1: -------------------------------------------------------------------------------- 1 | #requires -version 5.1 2 | 3 | #region main code 4 | 5 | 6 | #endregion 7 | 8 | #functions to export 9 | $functions = @() 10 | 11 | Export-Modulemember -function $functions -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/plastermanifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | MyProject 6 | b56d189d-701c-4035-82f4-23d83616699c 7 | 1.1.0 8 | MyProject 9 | My PowerShell Project Template 10 | Jeff Hicks 11 | module 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------- 27 | | Scaffolding your PowerShell project | 28 | --------------------------------------- 29 | | |\ /| | /| /| |\ | | / \ | 30 | | |/ \| | / |/ | | \ | |/ \| 31 | 32 | Creating your module manifest for ${PLASTER_PARAM_ModuleName} 33 | 34 | 35 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | A Pester test has been created to validate the module's manifest file. Add additional tests to the test directory. You can run the Pester tests in your project by executing the 'test' task. Press Ctrl+P, then type 'task test'. 59 | 60 | 61 | 62 | Invoke the myFunction Plaster manifest to begin scaffolding commands. 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /scripting/myTemplates/myProject/test/Module.T.ps1: -------------------------------------------------------------------------------- 1 | $ModuleManifestName = '<%=$PLASTER_PARAM_ModuleName%>.psd1' 2 | $ModuleManifestPath = "$PSScriptRoot\..\$ModuleManifestName" 3 | 4 | Describe '<%=$PLASTER_PARAM_ModuleName%> Manifest Tests' { 5 | It 'Passes Test-ModuleManifest' { 6 | Test-ModuleManifest -Path $ModuleManifestPath | Should Not BeNullOrEmpty 7 | $? | Should Be $true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scripting/myTemplates/myTemplates.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdhitsolutions/PSAutomationWorkshop/bd6e400b1dd8a8421ceca300096e5c4c6490c501/scripting/myTemplates/myTemplates.psd1 -------------------------------------------------------------------------------- /scripting/myTemplates/myTemplates.psm1: -------------------------------------------------------------------------------- 1 | # this is an empty root module as this module is just a 2 | # placeholder for my Plaster templates -------------------------------------------------------------------------------- /scripting/myTemplates/readme.txt: -------------------------------------------------------------------------------- 1 | copy this folder to c:\program files\windowspowershell\modules -------------------------------------------------------------------------------- /scripting/myTemplates/tests/myproject.tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | $template = "$psscriptroot/../myProject/plastermanifest.xml" 3 | 4 | [xml]$xml = Get-Content -Path $template 5 | $manifest = $xml.plastermanifest 6 | Describe "myProject Manifest" { 7 | It "Should pass Test-PlasterManifest" { 8 | {Test-PlasterManifest -Path $template} | Should Not Throw 9 | } 10 | It "Should be a Project template" { 11 | $manifest.templatetype | Should be "Project" 12 | } 13 | Context "Testing Parameters" { 14 | $params = $manifest.parameters.parameter 15 | 16 | $prompts = "ModuleName","Version","Description","PSVersion","Editor" 17 | foreach ($item in $prompts) { 18 | It "Should prompt for $item" { 19 | $params.name | Should Contain $item 20 | } 21 | } 22 | 23 | It "Should have a default module version of 0.1.0" { 24 | $node= $manifest.Parameters.SelectNodes("*[@name='Version']") 25 | $node.default | Should be "0.1.0" 26 | } 27 | 28 | It "Should default the author name to 'User-Fullname' " { 29 | $node= $manifest.Parameters.SelectNodes("*[@name='ModuleAuthor']") 30 | $node.type | Should be "user-fullname" 31 | } 32 | 33 | It "Should include an editor choice of VSCode" { 34 | $node= $manifest.Parameters.SelectNodes("*[@name='Editor']") 35 | $node.choice.value | Should contain "VSCode" 36 | } 37 | } #parameters context 38 | 39 | context Content { 40 | $content = $manifest.content 41 | 42 | It "Should create a module manifest" { 43 | $content.newModuleManifest | Should not be $null 44 | } 45 | 46 | It "Should create a docs folder" { 47 | $content.file.destination | Should contain "docs" 48 | } 49 | 50 | it "Should create an en-us folder" { 51 | $content.file.destination | Should contain "en-us" 52 | } 53 | 54 | it "Should copy a psm1 file from source" { 55 | $content.file.source | Should contain 'module.psm1' 56 | } 57 | 58 | $temps = "changelog.txt","README.md","license.txt" 59 | foreach ($file in $temps) { 60 | It "Should create $file from a template file" { 61 | $content.templateFile.source | Should contain $file 62 | $content.templateFile.destination | Should contain $file 63 | 64 | } 65 | } 66 | It "Should create a Pester test" { 67 | $content.SelectNodes("//*[contains(@source,'test')]") | should not be null 68 | } 69 | 70 | 71 | It "Should require the Pester module" { 72 | #$content.requireModule.SelectNodes("*[@name='Pester']") | should not be null 73 | $content.requireModule.name | should contain "Pester" 74 | } 75 | } #content context 76 | } 77 | --------------------------------------------------------------------------------