diff --git a/ErrorHandlingDebugging-McElreath/README.md b/ErrorHandlingDebugging-McElreath/README.md new file mode 100644 index 0000000..cae9ad6 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/README.md @@ -0,0 +1,7 @@ +# Take your PowerShell to the next level with error handling and debugging + +There are a lot of great features for debugging PowerShell withing VSCode. And if you're like me.... you never use them.... + +Let's change that today. I've made it my goal this year to be better and more consistent with how I debug my code and handle errors in my scripts. + +In this session, we're going to explore how errors are surfaced in PowerShell and how to handle those errors responsibly. We will also dive into using the debugger in VSCode to step through some PowerShell code to identify the potential sources of our errors. \ No newline at end of file diff --git a/ErrorHandlingDebugging-McElreath/demo/1-Error-Variable.ps1 b/ErrorHandlingDebugging-McElreath/demo/1-Error-Variable.ps1 new file mode 100644 index 0000000..c1d59a4 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/1-Error-Variable.ps1 @@ -0,0 +1,91 @@ + +$Error.Clear() + +# Let's Generate some errors +1/0 +Get-ChildItem c:\Does-Not-Exist +Get-WrongCommand + +# Returns an array of error records, with the most recent error at index 0 +$Error + +# Most recent error +$Error[0] + +# The oldest error +$Error[-1] + +# Can be used in a catch block to get the current error +$_ + +try { + 1/0 +} catch { + Write-Host "An error occurred: $_" +} + +# Was the last command successful? +Get-WrongCommand +$? + +# Clear the error variable +$Error.Clear() +$Error + +# Generate some more errors +Get-ChildItem c:\Does-Not-Exist + +# Error view options +$ErrorView # ConciseView, CategoryView, DetailedView, NormalView + +$ErrorView = 'DetailedView' + +Get-ChildItem c:\Does-Not-Exist + + +$ErrorView = 'ConciseView' + + +# Get error details +$Error[0] + +$Error[0] | Select-Object * + +# Get the most recent error record with more information +Get-Error -Newest 1 + +# Get a text dump of the most recent error record +$Error[0] | Format-Custom -Force + + +$Error[0] | Select-Object * + +$Error[0].Exception +$Error[0].InvocationInfo +$Error[0].ScriptStackTrace +$Error[0].Exception.StackTrace + + + + +$Error.Clear() +cls + +# ScriptStackTrace example +function Trace-Test { + function Get-BadPath { Get-ChildItem c:\Does-Not-Exist } + + function Function1 { Get-BadPath } + + function Function2 { Function1 } + + function Function3 { Function2 } + + Function3 + +} + +Trace-Test + +$error[0] | Select-Object * + diff --git a/ErrorHandlingDebugging-McElreath/demo/2-Terminating-Errors.ps1 b/ErrorHandlingDebugging-McElreath/demo/2-Terminating-Errors.ps1 new file mode 100644 index 0000000..1f558eb --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/2-Terminating-Errors.ps1 @@ -0,0 +1,85 @@ + +$Error.Clear() +cls + + +function Test-Error { + Get-ChildItem c:\Does-Not-Exist + + Write-Host "Will this Print?" -ForegroundColor Magenta +} + +Test-Error + + +function Test-Error { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop + + Write-Host "Will this Print?" -ForegroundColor Magenta +} + +Test-Error + + +$ErrorActionPreference # Continue, SilentlyContinue, Ignore, Stop, Break, Inquire, Suspend + + +function Test-Error { + param ( + [ValidateSet("Break", "Continue", "Ignore", "Stop", "SilentlyContinue", "Inquire" , "Suspend")] + $errorAction = "Continue" + ) + $variable1 = "Hello"; $variable2 = "Summit 2026!" + + Get-ChildItem c:\Does-Not-Exist -ErrorAction $errorAction + + Write-Host "Will this Print?" -ForegroundColor Magenta +} + +Test-Error + +Test-Error -ErrorAction "Stop" +Test-Error -ErrorAction "SilentlyContinue" +Test-Error -ErrorAction "Ignore" # Does not add an error record to the $Error variable +Test-Error -ErrorAction "Suspend" # Only used in PowerShell Workflows +Test-Error -ErrorAction "Inquire" +Test-Error -ErrorAction "Break" + + + +function Test-Error { + $ErrorActionPreference = "Stop" + + Get-ChildItem c:\Does-Not-Exist + + Write-Host "This will not be printed because ErrorActionPreference is set to Stop" -ForegroundColor Magenta +} + +Test-Error + + +function Test-Error { + throw("An error occurred in Test-Error") + + Write-Host "This will not be printed after a throw statement" -ForegroundColor Magenta +} + +Test-Error + + + +$Error.Clear() +$Error +cls + +function Test-Error { + $ErrorActionPreference = "SilentlyContinue" + + Get-ChildItem c:\Does-Not-Exist + + Write-Host "This will be printed because ErrorActionPreference is set to SilentlyContinue" -ForegroundColor Magenta +} + +Test-Error + +$Error \ No newline at end of file diff --git a/ErrorHandlingDebugging-McElreath/demo/3-Try-Catch.ps1 b/ErrorHandlingDebugging-McElreath/demo/3-Try-Catch.ps1 new file mode 100644 index 0000000..0dccbea --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/3-Try-Catch.ps1 @@ -0,0 +1,229 @@ + +$Error.Clear() +cls + + +# Try/Catch Syntax +try { + ## Try some code +} catch { + ## Code that runs after catching Terminating Errors +} finally { + ## Code that runs ALWAYS (success or failure) +} + + + +# Catching a terminating Error +try { + 1/0 + Write-Host "Will this print?" -ForegroundColor Green +} catch { + Write-Host "An error occurred: $_" -ForegroundColor Magenta +} + + +# Will this catch the error? +try { + Get-ChildItem c:\Does-Not-Exist + Write-Host "Will this print?" -ForegroundColor Green +} catch { + Write-Host "An error occurred: $_" -ForegroundColor Magenta +} + + +# Forcing the catch with -ErrorAction Stop +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop + Write-Host "Will this print?" -ForegroundColor Green +} catch { + Write-Host "An error occurred: $_" -ForegroundColor Magenta +} + +# Getting Error Info +$Error[0] +$Error[0] | Select-Object * + +# Get the latest Error with Get-Error +Get-Error -Newest 1 + + +$Error.Clear() +cls + + +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch [System.Management.Automation.ItemNotFoundException] { + Write-Host "The item was not found." -ForegroundColor Magenta +} catch { + Write-Host "A different error occurred: $_" -ForegroundColor Red +} + + +Get-Error -Newest 1 + +# Catch the ItemNotFoundException +try { + Get-ChildItem C:\code -ErrorAction Stop + 1/0 +} catch [System.Management.Automation.ItemNotFoundException] { + Write-Host "The item was not found." -ForegroundColor Magenta +} catch { + Write-Host "A different error occurred: $_" -ForegroundColor Red +} + + +# Catching multiple error types +try { + 1/0 +} catch [System.Management.Automation.ItemNotFoundException] { + Write-Host "The item was not found." -ForegroundColor Magenta +} catch [System.DivideByZeroException] { + Write-Host "A divide by zero error occurred" -ForegroundColor Red +} catch { + Write-Host "An even different error occurred: $_" -ForegroundColor DarkGreen +} + + +try { + Get-ChildItem2 c:\Does-Not-Exist +} catch [System.Management.Automation.ItemNotFoundException] { + Write-Host "The item was not found." -ForegroundColor Magenta +} catch [System.DivideByZeroException] { + Write-Host "A divide by zero error occurred" -ForegroundColor Red +} catch { + Write-Host "An even different error occurred" -ForegroundColor DarkGreen +} + + +$Error.Clear() +cls + + +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + throw +} + +$Error[0] | Select-Object * + +$Error.Clear() +cls + + +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + Write-Error $_ +} + +$Error[0] + +$Error[0] | Select-Object * + + +$Error.Clear() + +# Throw vs. Throw "Custom Message" vs. Write-Error +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + throw + # throw "A custom error message" + # Write-Error "This is a Write-Error message." + Write-Host "Will this print?" -ForegroundColor Magenta +} + +$Error[0] | Select-Object * + +$Error[1] | Select-Object * + + +$Error.Clear() + +# Throw vs. Throw "Custom Message" vs. Write-Error +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + Write-Error "This is a Write-Error message." -ErrorAction Stop + Write-Host "Will this print?" -ForegroundColor Magenta +} + + +$Error.Clear() +cls + + +$array = @('.\1.txt', '.\2.txt', '.\3.txt') + +# Item-specific Error Messages +foreach ($file in $array) { + try { + Start-Sleep -Seconds 1 + Get-ChildItem -Path $file -ErrorAction Stop + } catch { + Write-Error "Error accessing $file : $_" + # Throw("Error accessing $file : $_") + } +} + +$Error + +$Error.Count + +$Error[0] +$Error[1] + + +$Error.Clear() +cls + +# Using the caller's ErrorActionPreference +$ErrorActionPreference = 'Inquire' + +function Test-Error { + $callerErrorActionPreference = $ErrorActionPreference + try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop + } catch { + Write-Error -ErrorRecord $_ -ErrorAction $callerErrorActionPreference + Write-Host "Will this Print?" -ForegroundColor Magenta + } +} + +Test-Error + + + +$ErrorActionPreference = 'Continue' + + +# Finally +Try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + throw "An error occurred: The specified path does not exist." +} finally { + Write-Host "This will always execute, even if an error is thrown." -ForegroundColor Magenta +} + + + +# Throw error with an Exception Type +Try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop +} catch { + throw [System.Management.Automation.ItemNotFoundException]::new("Custom Message") +} finally { + Write-Host "This will always execute, even if an error is thrown." -ForegroundColor Magenta +} + +$Error[0] | Select-Object * + + + + + + diff --git a/ErrorHandlingDebugging-McElreath/demo/4-Debugging-0.ps1 b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-0.ps1 new file mode 100644 index 0000000..adbfba9 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-0.ps1 @@ -0,0 +1,22 @@ + + +function Test-Error { + param ( + [ValidateSet("Break", "Continue", "Ignore", "Stop", "SilentlyContinue", "Inquire" , "Suspend")] + $errorAction = "Continue" + ) + $variable1 = "Hello" + $variable2 = "Summit 2026!" + + Get-ChildItem c:\Does-Not-Exist -ErrorAction $errorAction + + Write-Host "Will this Print?" -ForegroundColor Magenta +} + + +Test-Error -ErrorAction "Inquire" + +Test-Error -ErrorAction "Break" + + + diff --git a/ErrorHandlingDebugging-McElreath/demo/4-Debugging-1.ps1 b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-1.ps1 new file mode 100644 index 0000000..b75d50d --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-1.ps1 @@ -0,0 +1,22 @@ +function Do-Something { + $variable1 = "Hello" + $variable2 = "World" + Write-Host "Doing something..." +} + +Do-Something + +## Debugger Options: + +# Continue - F5 - Continues execution until the next breakpoint +# Step-Into - F10 - Steps into the next function call +# Step-Over - F11 - Steps over the next function call +# Step-Out - Shift+F11 - Steps out of the current function +# Restart - Ctrl+Shift+F5 - Restarts the debugging session +# Stop - Shift+F5 - Stops the debugger + + + + + + diff --git a/ErrorHandlingDebugging-McElreath/demo/4-Debugging-2.ps1 b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-2.ps1 new file mode 100644 index 0000000..cdc13d5 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-2.ps1 @@ -0,0 +1,12 @@ + +function Do-Something-Else { + param ( + [string]$param1, + [string]$param2 + ) + $variable1 = $param1 + $variable2 = $param2 + Write-Host "Doing something with parameters: $param1, $param2" +} + +Do-Something-Else -param1 "Hello" -param2 "World" \ No newline at end of file diff --git a/ErrorHandlingDebugging-McElreath/demo/4-Debugging-3.ps1 b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-3.ps1 new file mode 100644 index 0000000..4355f54 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/4-Debugging-3.ps1 @@ -0,0 +1,12 @@ + +Import-Module C:\code\summit-2026\Toolmaking\module\Summit-Toolbox\ -Force + +$serviceName = "bits" +$action = "Restart" + + +Update-SummitService -ServiceName $serviceName -Action $action + +Write-Host "Done" + + diff --git a/ErrorHandlingDebugging-McElreath/demo/test.ps1 b/ErrorHandlingDebugging-McElreath/demo/test.ps1 new file mode 100644 index 0000000..dbafc2b --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/test.ps1 @@ -0,0 +1,3 @@ +1/0 +Get-ChildItem c:\Does-Not-Exist +Get-WrongCommand \ No newline at end of file diff --git a/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Commands.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Commands.ps1 new file mode 100644 index 0000000..ccd346b --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Commands.ps1 @@ -0,0 +1,27 @@ + +# Using Wait-Debugger to pause execution until a debugger is attached +.\z-CLI-Debug-Script.ps1 + + + +# Create a breakpoint on line 4 of the script +$breakPoint = Set-PSBreakpoint -Script ".\z-CLI-Debug-Script.ps1" -Line 4 +$breakPoint + + + +# List all breakpoints to verify it was set +Get-PSBreakpoint -Script ".\z-CLI-Debug-Script.ps1" + + +$ErrorActionPreference = 'break' + + +.\z-CLI-Debug-Script.ps1 + +$ErrorActionPreference = 'Continue' + +# Remove the breakpoint +Remove-PSBreakpoint -Breakpoint $breakpoint + +Get-PSBreakpoint -Script ".\z-CLI-Debug-Script.ps1" diff --git a/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Script.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Script.ps1 new file mode 100644 index 0000000..6f47ae9 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-CLI-Debug-Script.ps1 @@ -0,0 +1,9 @@ + + + +$variable1 = "Hello" +$variable2 = "World" + +Wait-Debugger + +Write-Host "Doing something with parameters: $param1, $param2" diff --git a/ErrorHandlingDebugging-McElreath/demo/z-Errors-Fix.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-Errors-Fix.ps1 new file mode 100644 index 0000000..ec32ffd --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-Errors-Fix.ps1 @@ -0,0 +1,68 @@ + + +try { + $item = Get-ChildItem -Path "C:\NonExistentDirectory" -ErrorAction Stop +} catch { + throw("An error occurred while trying to access the directory.") +} + + +Get-Service -Name "NonExistentService" + +1/0 + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Write-Error "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + +$null.tostring() + + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +1/0 + +$null.tostring() + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +Write-Error "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + +1/0 + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +$null.tostring() + + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +1/0 + +$null.tostring() + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +1/0 + +$null.tostring() + diff --git a/ErrorHandlingDebugging-McElreath/demo/z-Errors.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-Errors.ps1 new file mode 100644 index 0000000..c4fc84e --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-Errors.ps1 @@ -0,0 +1,62 @@ + +$item = Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +1/0 + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Write-Error "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + +$null.tostring() + + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +1/0 + +$null.tostring() + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +Write-Error "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + +1/0 + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +$null.tostring() + + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +1/0 + +$null.tostring() + +$random = Get-Random -Maximum 3 -Minimum 1 +Start-Sleep -Seconds $random + +Get-ChildItem -Path "C:\NonExistentDirectory" + +Get-Service -Name "NonExistentService" + +1/0 + +$null.tostring() + diff --git a/ErrorHandlingDebugging-McElreath/demo/z-extras.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-extras.ps1 new file mode 100644 index 0000000..592a195 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-extras.ps1 @@ -0,0 +1,5 @@ +trap { Write-Host "An error occurred: $_" -ForegroundColor Magenta } + + +$ErrorView = 'DetailedView' + diff --git a/ErrorHandlingDebugging-McElreath/demo/z-trap-1.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-trap-1.ps1 new file mode 100644 index 0000000..cc4832c --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-trap-1.ps1 @@ -0,0 +1,6 @@ + +trap { throw "An error occurred: $_" } + +Get-ChildItem -Path "C:\NonExistentDirectory" -ErrorAction Stop + +Write-Host "Done" \ No newline at end of file diff --git a/ErrorHandlingDebugging-McElreath/demo/z-trap-2.ps1 b/ErrorHandlingDebugging-McElreath/demo/z-trap-2.ps1 new file mode 100644 index 0000000..f15caf1 --- /dev/null +++ b/ErrorHandlingDebugging-McElreath/demo/z-trap-2.ps1 @@ -0,0 +1,15 @@ + +trap { + # Throw "An error occurred: $_" -ForegroundColor Magenta + Write-Host "An Error Ocurred. But I don't care. Moving on..." +} + +Import-Module C:\code\summit-2026\Toolmaking\module\Summit-Toolbox\ -Force + +Invoke-Nonsense -Path c:\Does-Not-Exist + +Write-Host "Done" + + + + diff --git a/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pdf b/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pdf new file mode 100644 index 0000000..fcd1f03 Binary files /dev/null and b/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pdf differ diff --git a/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pptx b/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pptx new file mode 100644 index 0000000..d17b4be Binary files /dev/null and b/ErrorHandlingDebugging-McElreath/slides/ErrorHandlingDebugging-McElreath.pptx differ diff --git a/FromScriptToTool-McElreath/README.md b/FromScriptToTool-McElreath/README.md new file mode 100644 index 0000000..5a44bbb --- /dev/null +++ b/FromScriptToTool-McElreath/README.md @@ -0,0 +1,9 @@ +# From Script to Tool: Building Robust, Meaningful Tools with PowerShell + +I have been asked the same question many times over the years and it usually goes something like this: “How do I know when to do something through the command line, a script, a function or a module for something in PowerShell?” + +The short answer, is “YES”. But the deeper question is, “What kind of Tool are you trying to build?” + +When we think of “Tools”, they can come in all sorts of shapes, sizes and uses. PowerShell tools are no different and there are usually no wrong answers here. + +In this session, we will explore the evolution of building tools in PowerShell. From trial and error at the command line, to crafting a complete module. I will use some examples from my own experience when it comes to creating re-usable tools with our favorite scripting language and we will watch how a small one liner can evolve into a tool that you and your colleagues can’t remember how you lived without. diff --git a/FromScriptToTool-McElreath/demo/1-Command-Line.ps1 b/FromScriptToTool-McElreath/demo/1-Command-Line.ps1 new file mode 100644 index 0000000..f1b5c84 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/1-Command-Line.ps1 @@ -0,0 +1,37 @@ + +# Exploring commands and parameters in PowerShell + +# Identify the command to get user information from Active Directory +Get-ADUser + +# Get detailed information about the command and its parameters +Update-Help + +Get-Help Get-ADUser +Get-Help Get-ADUser -Examples +Get-Help Get-ADUser -Full + +# Getting all users in Active Directory +Get-ADUser -Filter * + +# Getting all users with a specific name +Get-ADUser -Filter "Name -eq 'Dale Cooper'" + +Get-ADUser -Filter "Name -eq 'Dale Cooper'" | Select-Object * + +# Getting all properties for a specific user +Get-ADUser -Filter "Name -eq 'Dale Cooper'" -Properties * + +# Filtering and selecting specific properties +Get-ADUser -Filter * -SearchBase "OU=summit-users,DC=home,DC=lab" | Select-Object Name,SamAccountName,UserPrincipalName + +# Grouping users by their office location and sorting by count +Get-ADUser -Filter * -Properties * | Group-Object Office | Sort-Object Count -Descending + +# Filtering users based on office location and selecting specific properties +Get-ADUser -Filter "Office -eq 'Twin Peaks'" -Properties * | Select-Object Name,Office,Description,whenCreated,LastLogonDate | Format-Table + +# Exporting the results to a CSV file +Get-ADUser -Filter "Office -eq 'Twin Peaks'" -Properties * | + Select-Object Name,Office,Description,whenCreated,LastLogonDate | Export-Csv -Path ".\ADUsers.csv" -NoTypeInformation + diff --git a/FromScriptToTool-McElreath/demo/2-Script-Basic1.ps1 b/FromScriptToTool-McElreath/demo/2-Script-Basic1.ps1 new file mode 100644 index 0000000..678c4af --- /dev/null +++ b/FromScriptToTool-McElreath/demo/2-Script-Basic1.ps1 @@ -0,0 +1,19 @@ +######################### +## Basic Script Example: +######################### + +$credential = Get-Credential -UserName "home\matt" -Message "Enter password for home\matt" + +Invoke-Command -ComputerName "dc" -Credential $credential -ScriptBlock { + Get-Service -Name "bits" +} + +Invoke-Command -ComputerName "dc" -Credential $credential -ScriptBlock { + Start-Service -Name "bits" + Get-Service -Name "bits" +} + +Invoke-Command -ComputerName "dc" -Credential $credential -ScriptBlock { + Stop-Service -Name "bits" + Get-Service -Name "bits" +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/2-Script-Basic2-0.ps1 b/FromScriptToTool-McElreath/demo/2-Script-Basic2-0.ps1 new file mode 100644 index 0000000..72f23e3 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/2-Script-Basic2-0.ps1 @@ -0,0 +1,32 @@ +######################### +## Basic Script with hard coded variables: +######################### + +$ComputerName = "dc" +$ServiceName = "bits" + +$credential = Get-Credential -Message "Enter credentials for $ComputerName" -UserName "home\matt" + +$service = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Get-Service -Name $using:ServiceName +} + +$service | Format-Table Name, Status, StartType + +if ($service.Status -eq "Running") { + Write-Warning "The $($service.Name) service is running on $ComputerName. Stopping the service..." + Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Stop-Service -Name $using:ServiceName + } +} else { + Write-Warning "The $($service.Name) service is not running on $ComputerName. Starting the service..." + Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Start-Service -Name $using:ServiceName + } +} + +$serviceStatus = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Get-Service -Name $using:ServiceName +} + +$serviceStatus | Format-Table Name, Status, StartType \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Start.ps1 b/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Start.ps1 new file mode 100644 index 0000000..fb3ad62 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Start.ps1 @@ -0,0 +1,14 @@ +######################### +## Basic Script with hard coded variables: +######################### + +$ComputerName = "dc" +$ServiceName = "bits" + +$credential = Get-Credential -Message "Enter credentials for $ComputerName" -UserName "home\matt" + +Write-Warning "Starting the service $ServiceName on $ComputerName." +Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Start-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName | Format-Table Name, Status, StartType +} diff --git a/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Stop.ps1 b/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Stop.ps1 new file mode 100644 index 0000000..16680d1 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/2-Script-Basic2-1-Stop.ps1 @@ -0,0 +1,14 @@ +######################### +## Basic Script with hard coded variables: +######################### + +$ComputerName = "dc" +$ServiceName = "bits" + +$credential = Get-Credential -Message "Enter credentials for $ComputerName" -UserName "home\matt" + +Write-Warning "Stopping the service $ServiceName on $ComputerName." +Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Stop-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName | Format-Table Name, Status, StartType +} diff --git a/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-1.ps1 b/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-1.ps1 new file mode 100644 index 0000000..9d19ad5 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-1.ps1 @@ -0,0 +1,37 @@ +######################### +## Script using parameters for flexibility: +######################### + +param ( + $ComputerName, + $ServiceName, + $Action, + $Credential +) + +switch ($Action) { + "Start" { + Write-Warning "Starting the service $ServiceName on $ComputerName." + Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { + Start-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName | Format-Table Name, Status, StartType + } + } + "Stop" { + Write-Warning "Stopping the service $ServiceName on $ComputerName." + Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { + Stop-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName | Format-Table Name, Status, StartType + } + } + "Restart" { + Write-Warning "Restarting the service $ServiceName on $ComputerName." + Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock { + Restart-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName | Format-Table Name, Status, StartType + } + } + Default { + Write-Warning "Invalid action specified. Please use 'Start', 'Stop', or 'Restart'." + } +} diff --git a/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-2.ps1 b/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-2.ps1 new file mode 100644 index 0000000..6712ad7 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/3-Script-Basic3-param-2.ps1 @@ -0,0 +1,48 @@ +######################### +## Script that uses parameters, parameter types and parameter validation: +######################### + +param ( + [string]$ComputerName, + [string]$ServiceName, + [ValidateSet("Get", "Start", "Stop", "Restart")] + [string]$Action, + [PSCredential]$Credential +) + +switch ($Action.ToLower()) { + "get" { + $service = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Get-Service -Name $using:ServiceName + } + + $service | Format-Table Name, Status, StartType + } + "start" { + $service = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Start-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName + } + + $service | Format-Table Name, Status, StartType + } + "stop" { + $service = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Stop-Service -Name $using:ServiceName -Verbose + Get-Service -Name $using:ServiceName + } + + $service | Format-Table Name, Status, StartType + } + "restart" { + $service = Invoke-Command -ComputerName $ComputerName -Credential $credential -ScriptBlock { + Restart-Service -Name $using:ServiceName + Get-Service -Name $using:ServiceName + } + + $service | Format-Table Name, Status, StartType + } + default { + Write-Error "Invalid action specified. Use 'start', 'stop', or 'restart'." + } +} diff --git a/FromScriptToTool-McElreath/demo/4-Functions-1.ps1 b/FromScriptToTool-McElreath/demo/4-Functions-1.ps1 new file mode 100644 index 0000000..f9702fb --- /dev/null +++ b/FromScriptToTool-McElreath/demo/4-Functions-1.ps1 @@ -0,0 +1,86 @@ +######################### +## Script that uses comment based help documentation and a function: +######################### + + +<# +.SYNOPSIS +Manage Summit services remotely (get, start, stop, restart). + +.DESCRIPTION +This script provides small wrapper functions to get and control a named Windows +service on a remote computer using `Invoke-Command`. It exposes a simple +parameterized interface at the top-level so it can be used interactively or +as part of automation. + +.PARAMETER ComputerName +The target computer where the service is running. Accepts DNS name or IP. + +.PARAMETER ServiceName +The name of the Windows service to manage (as used by `Get-Service`). + +.PARAMETER Action +The action to perform. Valid values are: `Get`, `Start`, `Stop`, `Restart`. + +.PARAMETER Credential +A PSCredential object used for remote connections where alternate +credentials are required. + +.EXAMPLE +PS> .\3-Functions-1.ps1 -ComputerName srv01 -ServiceName "Spooler" -Action Get + +.EXAMPLE +PS> .\3-Functions-1.ps1 -ComputerName srv01 -ServiceName "Spooler" -Action Stop + +.EXAMPLE +PS> .\3-Functions-1.ps1 -ComputerName srv01 -ServiceName "Spooler" -Action Start + +.NOTES +Author: Generated documentation +#> +param ( + [string]$ComputerName, + [string]$ServiceName, + [ValidateSet("Get", "Start", "Stop", "Restart")] + [string]$Action, + [PSCredential]$Credential +) + +function Set-SummitService { + param ( + [string]$ComputerName, + [string]$ServiceName, + [string]$Action, + [PSCredential]$Credential + ) + + $scriptBlock = { + param ($ServiceName, $Action) + switch ($Action) { + "Get" { Get-Service -Name $ServiceName | Format-Table Name, Status, StartType } + "Start" { Start-Service -Name $ServiceName; Get-Service -Name $ServiceName | Format-Table Name, Status, StartType } + "Stop" { Stop-Service -Name $ServiceName; Get-Service -Name $ServiceName | Format-Table Name, Status, StartType } + "Restart" { Restart-Service -Name $ServiceName; Get-Service -Name $ServiceName | Format-Table Name, Status, StartType } + } + } + + Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ArgumentList $ServiceName, $Action +} + +switch ($Action.ToLower()) { + "get" { + Set-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Action "Get" -Credential $Credential + } + "start" { + Set-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Action "Start" -Credential $Credential + } + "stop" { + Set-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Action "Stop" -Credential $Credential + } + "restart" { + Set-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Action "Restart" -Credential $Credential + } + default { + Write-Error "Invalid action specified. Use 'start', 'stop', or 'restart'." + } +} diff --git a/FromScriptToTool-McElreath/demo/5-Modules.ps1 b/FromScriptToTool-McElreath/demo/5-Modules.ps1 new file mode 100644 index 0000000..49fe59f --- /dev/null +++ b/FromScriptToTool-McElreath/demo/5-Modules.ps1 @@ -0,0 +1,26 @@ +######################### +## using Modules: +######################### + +# Import the module +Import-Module .\module\Summit-Toolbox\ -Force + +# Get all commands available in the module +Get-Command -Module Summit-Toolbox + +# Run one of the commands +Get-SummitGreeting + +# Get help documentation of a command +Get-Help Update-SummitService + +# Running module commands +Update-SummitService -ServiceName bits -Action Get + +Update-SummitService -ServiceName bits -Action Start + +Update-SummitService -ServiceName bits -Action Stop + +Update-SummitService -ServiceName bits -Action Restart + +Get-BasicCommand -CommandName "Testing 123" \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/6-Outputs.ps1 b/FromScriptToTool-McElreath/demo/6-Outputs.ps1 new file mode 100644 index 0000000..7896f13 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/6-Outputs.ps1 @@ -0,0 +1,97 @@ + +# Write-Host vs. Write-Output +function Get-Stuff { + Write-Host "`nGetting some stuff..." -ForegroundColor Magenta + Write-Output "Name: Summit-2026, Location: Bellevue, WA, Version: 1.0" +} + +Get-Stuff + +$variable = Get-Stuff +$variable + +$variable | Get-Member + + +# JSON Output +function Get-Stuff { + $hashTable = @{ + "Name" = "Summit-2026" + "Location" = "Bellevue, WA" + "Version" = "1.0" + } + Write-Output $hashTable | ConvertTo-Json +} + +Get-Stuff + +Get-Stuff | Get-Member + +Get-Stuff | ConvertFrom-Json + +# Hashtable output +function Get-Stuff { + $hashTable = [Ordered]@{ + "Name" = "Summit-2026" + "Location" = "Bellevue, WA" + "Version" = "1.0" + } + Write-Output $hashTable +} + +Get-Stuff + +Get-Stuff | Get-Member + +# PSCustomObject output +function Get-Stuff { + $object = [PSCustomObject]@{ + "Name" = "Summit-2026" + "Location" = "Bellevue, WA" + "Version" = "1.0" + } + Write-Output $object +} + +Get-Stuff + +Get-Stuff | Get-Member + +Get-ChildItem ./1-Command-Line.ps1 | Get-Member + + +# Using other PS Objects as outputs +function Get-Stuff { + $services = Get-Service BITS, WinRM, W32Time, Spooler + + $runningServices = $services | Where-Object Status -eq "Running" + + Write-Output $runningServices +} + +Get-Stuff + +Get-Stuff | Get-Member + + + +function Get-Stuff { + $services = Get-Service BITS, WinRM, W32Time, Spooler + + $runningServices = $services | Where-Object Status -eq "Running" + + $object = [PSCustomObject]@{ + "RunningServices" = $runningServices + } + + Write-Output $object +} + +Get-Stuff + +Get-Stuff | Get-Member + +Get-Stuff | Select-Object -ExpandProperty RunningServices | Get-Member + +$services = Get-Stuff | Select-Object -ExpandProperty RunningServices +$services | Get-Service \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/7-ErrorHandling.ps1 b/FromScriptToTool-McElreath/demo/7-ErrorHandling.ps1 new file mode 100644 index 0000000..9ad25e3 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/7-ErrorHandling.ps1 @@ -0,0 +1,42 @@ + +# Clear the error variable +$Error.Clear() + +# Generate an error and inspect the $Error variable +Get-ChildItem c:\Does-Not-Exist + +$Error + +$ErrorView # ConciseView, CategoryView, DetailedView, NormalView + +$Error[0] | select * + + +# Non-Terminating Error in a try/catch block +try { + Get-ChildItem c:\Does-Not-Exist + Write-Host "Moving on to the next command..." -ForegroundColor Magenta +} catch { + throw "You are missing a directory. Please contact the service desk to resolve this issue." +} + +$ErrorActionPreference # Continue, SilentlyContinue, Ignore, Stop, Break, Inquire, Suspend + + +# Terminating Error in a try/catch block +try { + Get-ChildItem c:\Does-Not-Exist -ErrorAction Stop + 1/0 + Write-Host "Moving on to the next command..." -ForegroundColor Magenta +} catch { + throw "You are missing a directory. Please contact the service desk to resolve this issue." +} + + +# try/catch with a valid directory +try { + Get-ChildItem C:\code -ErrorAction Stop + Write-Host "Moving on to the next command..." -ForegroundColor Magenta +} catch { + throw "You are missing a directory. Please contact the service desk to resolve this issue." +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/9-CRUD.ps1 b/FromScriptToTool-McElreath/demo/9-CRUD.ps1 new file mode 100644 index 0000000..8dfe440 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/9-CRUD.ps1 @@ -0,0 +1,39 @@ + +# (R)ead operations +Get-ADUser -Filter "Office -eq 'Twin Peaks'" -Properties * | + Select-Object Name,Office,Description,whenCreated,LastLogonDate | Format-Table + +Get-ADUser -Identity "Dale.Cooper" -Properties * | + Select-Object Name,Office,Description,whenCreated,LastLogonDate + + +# (U)pdate operations +Set-ADUser -Identity "Dale.Cooper" ` + -Office "Seattle" ` + -Description "Moved to Seattle office" + +Get-ADUser -Identity "Dale.Cooper" -Properties * | + Select-Object Name,Office,Description,whenCreated,LastLogonDate + +# (C)reate operations +New-ADUser -Name "Pete Martell" ` + -GivenName "Pete" ` + -Surname "Martell" ` + -SamAccountName "Pete.Martell" ` + -Office "Twin Peaks" ` + -Description "Gone Fishin'" ` + -Path "OU=summit-users,DC=home,DC=lab" + +Get-ADUser -Identity "Pete.Martell" -Properties * | Select-Object Name,Office,Description,whenCreated,LastLogonDate + +# (D)elete operations +Remove-ADUser -Identity "Pete.Martell" + +Remove-ADUser -Identity "Pete.Martell" -Confirm:$false + + + + + +## Reset user +# Set-ADUser -Identity "Dale.Cooper" -Office "Twin Peaks" -Description "Special Agent" \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/ADUsers.csv b/FromScriptToTool-McElreath/demo/ADUsers.csv new file mode 100644 index 0000000..5622cd1 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/ADUsers.csv @@ -0,0 +1,5 @@ +"Name","Office","Description","whenCreated","LastLogonDate" +"Dougie Jones","Twin Peaks",,"3/29/2026 2:22:37 PM","4/5/2026 9:48:49 AM" +"Dale Cooper","Twin Peaks","Special Agent","3/29/2026 2:23:18 PM","4/5/2026 9:48:17 AM" +"James Hurley","Twin Peaks","Has always been cool","4/5/2026 9:23:36 AM","4/5/2026 9:42:39 AM" +"Donna Hayward","Twin Peaks",,"4/5/2026 9:37:22 AM", diff --git a/FromScriptToTool-McElreath/demo/gui.ps1 b/FromScriptToTool-McElreath/demo/gui.ps1 new file mode 100644 index 0000000..683ddca --- /dev/null +++ b/FromScriptToTool-McElreath/demo/gui.ps1 @@ -0,0 +1,258 @@ +# gui.ps1 +# GUI for managing AD users (CRUD operations) + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing +Import-Module ActiveDirectory + +# Create form +$form = New-Object System.Windows.Forms.Form +$form.Text = "AD User Management Tool" +$form.Size = New-Object System.Drawing.Size(800, 600) +$form.StartPosition = "CenterScreen" + +# Search section +$searchLabel = New-Object System.Windows.Forms.Label +$searchLabel.Location = New-Object System.Drawing.Point(10, 20) +$searchLabel.Size = New-Object System.Drawing.Size(100, 20) +$searchLabel.Text = "Search Filter:" +$form.Controls.Add($searchLabel) + +$searchTextBox = New-Object System.Windows.Forms.TextBox +$searchTextBox.Location = New-Object System.Drawing.Point(120, 20) +$searchTextBox.Size = New-Object System.Drawing.Size(200, 20) +$form.Controls.Add($searchTextBox) + +$searchButton = New-Object System.Windows.Forms.Button +$searchButton.Location = New-Object System.Drawing.Point(330, 20) +$searchButton.Size = New-Object System.Drawing.Size(75, 23) +$searchButton.Text = "Search" +$form.Controls.Add($searchButton) + +# DataGridView for results +$dataGridView = New-Object System.Windows.Forms.DataGridView +$dataGridView.Location = New-Object System.Drawing.Point(10, 60) +$dataGridView.Size = New-Object System.Drawing.Size(760, 200) +$dataGridView.AutoSizeColumnsMode = "Fill" +$dataGridView.ReadOnly = $true +$form.Controls.Add($dataGridView) + +# User details section +$userDetailsLabel = New-Object System.Windows.Forms.Label +$userDetailsLabel.Location = New-Object System.Drawing.Point(10, 280) +$userDetailsLabel.Size = New-Object System.Drawing.Size(100, 20) +$userDetailsLabel.Text = "User Details:" +$form.Controls.Add($userDetailsLabel) + +$nameLabel = New-Object System.Windows.Forms.Label +$nameLabel.Location = New-Object System.Drawing.Point(10, 310) +$nameLabel.Size = New-Object System.Drawing.Size(80, 20) +$nameLabel.Text = "Name:" +$form.Controls.Add($nameLabel) + +$nameTextBox = New-Object System.Windows.Forms.TextBox +$nameTextBox.Location = New-Object System.Drawing.Point(100, 310) +$nameTextBox.Size = New-Object System.Drawing.Size(200, 20) +$form.Controls.Add($nameTextBox) + +$samLabel = New-Object System.Windows.Forms.Label +$samLabel.Location = New-Object System.Drawing.Point(10, 340) +$samLabel.Size = New-Object System.Drawing.Size(80, 20) +$samLabel.Text = "Username:" +$form.Controls.Add($samLabel) + +$samTextBox = New-Object System.Windows.Forms.TextBox +$samTextBox.Location = New-Object System.Drawing.Point(100, 340) +$samTextBox.Size = New-Object System.Drawing.Size(200, 20) +$form.Controls.Add($samTextBox) + +$upnLabel = New-Object System.Windows.Forms.Label +$upnLabel.Location = New-Object System.Drawing.Point(10, 370) +$upnLabel.Size = New-Object System.Drawing.Size(80, 20) +$upnLabel.Text = "UPN:" +$form.Controls.Add($upnLabel) + +$upnTextBox = New-Object System.Windows.Forms.TextBox +$upnTextBox.Location = New-Object System.Drawing.Point(100, 370) +$upnTextBox.Size = New-Object System.Drawing.Size(200, 20) +$form.Controls.Add($upnTextBox) + +$passwordLabel = New-Object System.Windows.Forms.Label +$passwordLabel.Location = New-Object System.Drawing.Point(10, 400) +$passwordLabel.Size = New-Object System.Drawing.Size(80, 20) +$passwordLabel.Text = "Password:" +$form.Controls.Add($passwordLabel) + +$passwordTextBox = New-Object System.Windows.Forms.TextBox +$passwordTextBox.Location = New-Object System.Drawing.Point(100, 400) +$passwordTextBox.Size = New-Object System.Drawing.Size(200, 20) +$passwordTextBox.PasswordChar = '*' +$form.Controls.Add($passwordTextBox) + +# Buttons +$getButton = New-Object System.Windows.Forms.Button +$getButton.Location = New-Object System.Drawing.Point(350, 310) +$getButton.Size = New-Object System.Drawing.Size(75, 23) +$getButton.Text = "Get" +$form.Controls.Add($getButton) + +$createButton = New-Object System.Windows.Forms.Button +$createButton.Location = New-Object System.Drawing.Point(350, 340) +$createButton.Size = New-Object System.Drawing.Size(75, 23) +$createButton.Text = "Create" +$form.Controls.Add($createButton) + +$updateButton = New-Object System.Windows.Forms.Button +$updateButton.Location = New-Object System.Drawing.Point(350, 370) +$updateButton.Size = New-Object System.Drawing.Size(75, 23) +$updateButton.Text = "Update" +$form.Controls.Add($updateButton) + +$deleteButton = New-Object System.Windows.Forms.Button +$deleteButton.Location = New-Object System.Drawing.Point(350, 400) +$deleteButton.Size = New-Object System.Drawing.Size(75, 23) +$deleteButton.Text = "Delete" +$form.Controls.Add($deleteButton) + +# Clear button +$clearButton = New-Object System.Windows.Forms.Button +$clearButton.Location = New-Object System.Drawing.Point(450, 310) +$clearButton.Size = New-Object System.Drawing.Size(75, 23) +$clearButton.Text = "Clear" +$form.Controls.Add($clearButton) + +# Event handlers + +# Search button click event +$searchButton.Add_Click({ + $filter = $searchTextBox.Text + if ($filter -eq "") { + $filter = "*" + } + try { + $users = Get-ADUser -Filter { Name -like $filter } -SearchBase "OU=summit-users,DC=home,DC=lab" -Properties Name, SamAccountName, UserPrincipalName, Enabled + $dataGridView.DataSource = $null + $dataGridView.DataSource = $users | Select-Object Name, SamAccountName, UserPrincipalName, Enabled + $dataGridView.Refresh() + if ($users.Count -eq 0) { + [System.Windows.Forms.MessageBox]::Show("No users found matching '$filter'", "No Results") + } + } catch { + [System.Windows.Forms.MessageBox]::Show("Error querying AD: $($_.Exception.Message)", "Error") + } +}) + +# DataGridView selection changed event +$dataGridView.Add_SelectionChanged({ + if ($dataGridView.SelectedRows.Count -gt 0) { + $selectedUser = $dataGridView.SelectedRows[0].DataBoundItem + $nameTextBox.Text = $selectedUser.Name + $samTextBox.Text = $selectedUser.SamAccountName + $upnTextBox.Text = $selectedUser.UserPrincipalName + $passwordTextBox.Text = "" # Don't populate password + } +}) + +# Get button click event +$getButton.Add_Click({ + $sam = $samTextBox.Text + if ($sam -eq "") { + [System.Windows.Forms.MessageBox]::Show("Please enter a username to get user details.", "Input Required") + return + } + try { + $user = Get-ADUser -Identity $sam -Properties Name, SamAccountName, UserPrincipalName, Enabled + $nameTextBox.Text = $user.Name + $samTextBox.Text = $user.SamAccountName + $upnTextBox.Text = $user.UserPrincipalName + $passwordTextBox.Text = "" + [System.Windows.Forms.MessageBox]::Show("User details loaded.", "Success") + } catch { + [System.Windows.Forms.MessageBox]::Show("Error getting user: $($_.Exception.Message)", "Error") + } +}) + +# Create button click event +$createButton.Add_Click({ + $name = $nameTextBox.Text + $sam = $samTextBox.Text + $upn = $upnTextBox.Text + $password = $passwordTextBox.Text + + if ($name -eq "" -or $sam -eq "" -or $upn -eq "" -or $password -eq "") { + [System.Windows.Forms.MessageBox]::Show("Please fill in all fields to create a user.", "Input Required") + return + } + + try { + $securePassword = ConvertTo-SecureString $password -AsPlainText -Force + New-ADUser -Name $name -SamAccountName $sam -UserPrincipalName $upn -AccountPassword $securePassword -Enabled $true -Path "OU=summit-users,DC=home,DC=lab" + [System.Windows.Forms.MessageBox]::Show("User '$sam' created successfully.", "Success") + # Refresh the grid + $searchButton.PerformClick() + } catch { + [System.Windows.Forms.MessageBox]::Show("Error creating user: $($_.Exception.Message)", "Error") + } +}) + +# Update button click event +$updateButton.Add_Click({ + $sam = $samTextBox.Text + $name = $nameTextBox.Text + $upn = $upnTextBox.Text + + if ($sam -eq "") { + [System.Windows.Forms.MessageBox]::Show("Please enter a username to update.", "Input Required") + return + } + + try { + $params = @{} + if ($name -ne "") { $params.Name = $name } + if ($upn -ne "") { $params.UserPrincipalName = $upn } + + if ($params.Count -gt 0) { + Set-ADUser -Identity $sam @params + [System.Windows.Forms.MessageBox]::Show("User '$sam' updated successfully.", "Success") + # Refresh the grid + $searchButton.PerformClick() + } else { + [System.Windows.Forms.MessageBox]::Show("No changes to update.", "No Changes") + } + } catch { + [System.Windows.Forms.MessageBox]::Show("Error updating user: $($_.Exception.Message)", "Error") + } +}) + +# Delete button click event +$deleteButton.Add_Click({ + $sam = $samTextBox.Text + if ($sam -eq "") { + [System.Windows.Forms.MessageBox]::Show("Please enter a username to delete.", "Input Required") + return + } + + $result = [System.Windows.Forms.MessageBox]::Show("Are you sure you want to delete user '$sam'?", "Confirm Delete", [System.Windows.Forms.MessageBoxButtons]::YesNo) + if ($result -eq [System.Windows.Forms.DialogResult]::Yes) { + try { + Remove-ADUser -Identity $sam -Confirm:$false + [System.Windows.Forms.MessageBox]::Show("User '$sam' deleted successfully.", "Success") + # Clear fields and refresh grid + $clearButton.PerformClick() + $searchButton.PerformClick() + } catch { + [System.Windows.Forms.MessageBox]::Show("Error deleting user: $($_.Exception.Message)", "Error") + } + } +}) + +# Clear button click event +$clearButton.Add_Click({ + $nameTextBox.Text = "" + $samTextBox.Text = "" + $upnTextBox.Text = "" + $passwordTextBox.Text = "" +}) + +# Show form +$form.ShowDialog() diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psd1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psd1 new file mode 100644 index 0000000..26dbf79 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psd1 @@ -0,0 +1,125 @@ +# Summit-Toolbox.psd1 +# Module manifest for Summit-Toolbox + +@{ + # Script module or binary module file associated with this manifest. + RootModule = 'Summit-Toolbox.psm1' + + # Version number of this module. + ModuleVersion = '1.0.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = '12345678-1234-1234-1234-123456789012' + + # Author of this module + Author = 'Summit 2026 Participant' + + # Company or vendor of this module + CompanyName = 'Summit 2026' + + # Copyright statement for this module + Copyright = '(c) 2026 Summit 2026. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'A PowerShell module containing tools for Summit 2026 Toolmaking.' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '5.1' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there is nothing to export + FunctionsToExport = @('Get-SummitGreeting', 'Update-SummitService', 'Get-SummitOfficeUser', 'New-SummitUser', 'Get-BasicCommand', 'Invoke-Nonsense') + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there is nothing to export + CmdletsToExport = @() + + # Variables to export from this module + VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there is nothing to export + AliasesToExport = @() + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @('Summit', 'Toolbox', 'PowerShell') + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psm1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psm1 new file mode 100644 index 0000000..c7ddf56 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/Summit-Toolbox.psm1 @@ -0,0 +1,26 @@ +# Summit-Toolbox.psm1 +# A PowerShell module for Summit 2026 Toolmaking + +function Get-SummitGreeting { + <# + .SYNOPSIS + Returns a greeting message for Summit 2026. + .DESCRIPTION + This function provides a simple greeting for the Summit 2026 event. + .EXAMPLE + Get-SummitGreeting + Returns: "Welcome to Summit 2026!" + #> + param() + + "Welcome to Summit 2026!" +} + +#Dot source private functions. +Get-ChildItem -Path $PSScriptRoot\Functions\Private\*.ps1 | ForEach-Object { . $_.FullName } + +#Dot source public functions. +Get-ChildItem -Path $PSScriptRoot\Functions\Public\*.ps1 | ForEach-Object { . $_.FullName } + +# Export the functions +Export-ModuleMember -Function Get-SummitGreeting, Update-SummitService, Get-SummitOfficeUser, New-SummitUser, Get-BasicCommand, Invoke-Nonsense \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Get-SummitService.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Get-SummitService.ps1 new file mode 100644 index 0000000..be34995 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Get-SummitService.ps1 @@ -0,0 +1,12 @@ +function Get-SummitService { + param ( + [string]$ServiceName, + [PSCredential]$Credential + ) + + Write-Warning "Getting service '$ServiceName'..." + + $service = Get-Service -Name $ServiceName + + Write-Host "Service '$ServiceName' status: $($service.Status)" +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Restart-SummitService.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Restart-SummitService.ps1 new file mode 100644 index 0000000..54ba306 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Restart-SummitService.ps1 @@ -0,0 +1,17 @@ +function Restart-SummitService { + param ( + [string]$ServiceName, + [PSCredential]$Credential + ) + + if ($ServiceName -eq "bits") { + $ServiceName = "bits-error" + } + + Write-Warning "Restarting service '$ServiceName'..." + + $service = Get-Service -Name $ServiceName + + Write-Host "Service '$ServiceName' status: Running" + +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Start-SummitService.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Start-SummitService.ps1 new file mode 100644 index 0000000..65daacf --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Start-SummitService.ps1 @@ -0,0 +1,12 @@ +function Start-SummitService { + param ( + [string]$ServiceName, + [PSCredential]$Credential + ) + + Write-Warning "Starting service '$ServiceName'..." + + $service = Get-Service -Name $ServiceName + + Write-Host "Service '$ServiceName' status: Running" +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Stop-SummitService.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Stop-SummitService.ps1 new file mode 100644 index 0000000..27aa259 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Private/Stop-SummitService.ps1 @@ -0,0 +1,13 @@ +function Stop-SummitService { + param ( + [string]$ServiceName, + [PSCredential]$Credential + ) + + Write-Warning "Stopping service '$ServiceName'" + + $service = Get-Service -Name $ServiceName + + Write-Host "Service '$ServiceName' status: Stopped" + +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-BasicCommand.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-BasicCommand.ps1 new file mode 100644 index 0000000..1ec813a --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-BasicCommand.ps1 @@ -0,0 +1,25 @@ +<# +.SYNOPSIS +Displays the provided command text. + +.DESCRIPTION +A minimal helper function used for demonstrations and testing. Writes the provided +`CommandName` string to the output stream. + +.PARAMETER CommandName +The text of the command to display. If omitted the function will output an empty string. + +.EXAMPLE +Get-BasicCommand -CommandName "Test" +Outputs: You entered the command: Test + +.NOTES +Module: Summit-Toolbox +#> +function Get-BasicCommand { + param ( + [string]$CommandName + ) + + Write-Output "You entered the command: $CommandName" +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-SummitOfficeUser.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-SummitOfficeUser.ps1 new file mode 100644 index 0000000..a529af5 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Get-SummitOfficeUser.ps1 @@ -0,0 +1,29 @@ +<# +.SYNOPSIS +Retrieve Summit office users from Active Directory. + +.DESCRIPTION +Searches the `OU=summit-users,DC=home,DC=lab` container for AD user objects and +returns users whose `Office` property matches the provided value. Requires the +ActiveDirectory module and appropriate domain read permissions. + +.PARAMETER Office +The Office name to filter users by (matches the `Office` AD attribute). + +.EXAMPLE +Get-SummitOfficeUser -Office "Seattle" +Returns users in the Summit users OU whose Office equals "Seattle". + +.NOTES +Module: Summit-Toolbox +#> +function Get-SummitOfficeUser { + [CmdletBinding()] + param ( + [string]$Office + ) + + Get-ADUser -Filter * -SearchBase "OU=summit-users,DC=home,DC=lab" -Properties Name,UserPrincipalName,Description,Office | + Where-Object Office -eq $Office | Select-Object Name,UserPrincipalName,Description,Office + +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Invoke-Nonsense.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Invoke-Nonsense.ps1 new file mode 100644 index 0000000..05de891 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Invoke-Nonsense.ps1 @@ -0,0 +1,20 @@ +function Invoke-Nonsense { + param ( + $path, + $process, + $Sevice + ) + + if ($path) { + Get-ChildItem -Path c:\Does-Not-Exist -ErrorAction Stop + } + + if ($process) { + Get-Process Nothing + } + + if ($service) { + Get-Service Unknown + } + +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/New-SummitUser.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/New-SummitUser.ps1 new file mode 100644 index 0000000..4d7a458 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/New-SummitUser.ps1 @@ -0,0 +1,19 @@ +function New-SummitUser { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Username, + + [Parameter(Mandatory = $true)] + [string]$Password + ) + + # Create a new user object + $user = New-Object PSObject -Property @{ + Username = $Username + Password = $Password + } + + # Return the new user object + return $user +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Update-SummitService.ps1 b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Update-SummitService.ps1 new file mode 100644 index 0000000..711d980 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/module/Summit-Toolbox/functions/Public/Update-SummitService.ps1 @@ -0,0 +1,54 @@ +<# +.SYNOPSIS +Perform service operations using Summit service helper functions. + +.DESCRIPTION +Wrapper that invokes the module's service helper functions (`Get-SummitService`, +`Start-SummitService`, `Stop-SummitService`, `Restart-SummitService`) against a +target computer and service name. Use `-Action` to choose the operation. + +.PARAMETER ComputerName +The target computer where the service is hosted. + +.PARAMETER ServiceName +The name of the service to act on. + +.PARAMETER Action +The action to perform. Valid values are `Get`, `Start`, `Stop`, `Restart`. + +.PARAMETER Credential +Optional PSCredential used for remote authentication when connecting to the target computer. + +.EXAMPLE +Update-SummitService -ComputerName "srv01" -ServiceName "SummitSvc" -Action Restart -Credential (Get-Credential) + +.NOTES +Relies on private helper functions in the module's `functions\Private` folder. +Module: Summit-Toolbox +#> +function Update-SummitService { + [CmdletBinding()] + param ( + [string]$ServiceName, + [ValidateSet("Get", "Start", "Stop", "Restart")] + [string]$Action + ) + + switch ($Action.ToLower()) { + "get" { + Get-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Credential $Credential + } + "start" { + Start-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Credential $Credential + } + "stop" { + Stop-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Credential $Credential + } + "restart" { + Restart-SummitService -ComputerName $ComputerName -ServiceName $ServiceName -Credential $Credential + } + default { + Write-Error "Invalid action specified. Use 'start', 'stop', or 'restart'." + } + } +} \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/setup/setup-users.ps1 b/FromScriptToTool-McElreath/demo/setup/setup-users.ps1 new file mode 100644 index 0000000..0fbc2db --- /dev/null +++ b/FromScriptToTool-McElreath/demo/setup/setup-users.ps1 @@ -0,0 +1,35 @@ +# setup-users.ps1 +# Create test users in OU=summit-users + +Import-Module ActiveDirectory + +$ou = "OU=summit-users,DC=home,DC=lab" +$domain = "home.lab" +$passwordPlain = (New-Guid).Guid +$securePassword = ConvertTo-SecureString $passwordPlain -AsPlainText -Force + +$firstNames = @("Avery","Bailey","Camden","Delaney","Emerson","Finley","Gray","Harper","Indigo","Jules") +$lastNames = @("Ashby","Bright","Clarke","Dawson","Ellis","Foster","Grady","Holloway","Iverson","Jenkins") + +for ($i = 0; $i -lt 10; $i++) { + $first = $firstNames[$i] + $last = $lastNames[$i] + $sam = "$first.$last".ToLower() + $upn = "$sam@$domain" + $display = "$first $last" + + New-ADUser -Name $display ` + -GivenName $first ` + -Surname $last ` + -SamAccountName $sam ` + -UserPrincipalName $upn ` + -DisplayName $display ` + -Path $ou ` + -AccountPassword $securePassword ` + -Enabled $true ` + -ChangePasswordAtLogon $false + + Write-Host "Created: $display ($upn)" +} + +Write-Host "Done creating users in $ou" diff --git a/FromScriptToTool-McElreath/demo/setup/setup-vars.ps1 b/FromScriptToTool-McElreath/demo/setup/setup-vars.ps1 new file mode 100644 index 0000000..aa3fc85 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/setup/setup-vars.ps1 @@ -0,0 +1,2 @@ + +$credential = Get-Credential -Message "Enter credentials for $ComputerName" -UserName "home\matt" \ No newline at end of file diff --git a/FromScriptToTool-McElreath/demo/setup/setup.ps1 b/FromScriptToTool-McElreath/demo/setup/setup.ps1 new file mode 100644 index 0000000..21b9127 --- /dev/null +++ b/FromScriptToTool-McElreath/demo/setup/setup.ps1 @@ -0,0 +1,8 @@ +# Install Chocolatey +Set-ExecutionPolicy Bypass -Scope Process -Force; ` + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; ` + iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + +# Install RSAT tools for AD cmdlets +choco install -y rsat + diff --git a/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pdf b/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pdf new file mode 100644 index 0000000..807a9f3 Binary files /dev/null and b/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pdf differ diff --git a/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pptx b/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pptx new file mode 100644 index 0000000..c9c53a5 Binary files /dev/null and b/FromScriptToTool-McElreath/slides/FromScriptToTool-McElreath.pptx differ