├── .github ├── FUNDING.yml └── workflows │ ├── BuildShowDemo.yml │ ├── GitPub.yml │ └── SendPSA.yml ├── Assets ├── ShowDemo-animated.svg ├── ShowDemo.svg ├── ShowDemo@1080p.png ├── demo.gif └── demo.mp4 ├── Build ├── GitHub │ ├── Actions │ │ └── DemoPowerShell.ps1 │ └── Jobs │ │ ├── BuildShowDemo.psd1 │ │ └── SendPSA.psd1 ├── ShowDemo.GitHubAction.PSDevOps.ps1 ├── ShowDemo.GitHubWorkflow.PSDevOps.ps1 ├── ShowDemo.HelpOut.ps1 ├── ShowDemo.PSA.ps1 ├── ShowDemo.PSSVG.ps1 └── ShowDemo.ezout.ps1 ├── CHANGELOG.md ├── Commands ├── Export-Demo.ps1 ├── Get-Demo.ps.ps1 ├── Get-Demo.ps1 ├── Import-Demo.ps.ps1 ├── Import-Demo.ps1 ├── Resume-Demo.ps1 └── Show-Demo.ps1 ├── Demos ├── Demo.demo.md ├── Demo.demo.ps1 ├── Trinity-Of-Discoverability.demo.md └── Trinity-Of-Discoverability.demo.ps1 ├── Dockerfile ├── LICENSE ├── README.md ├── ShowDemo.format.ps1xml ├── ShowDemo.ps.psm1 ├── ShowDemo.psd1 ├── ShowDemo.psm1 ├── ShowDemo.tests.ps1 ├── ShowDemo.types.ps1xml ├── Types ├── Demo.Chapter │ └── get_Steps.ps1 ├── Demo.Step │ ├── HidePrompt.ps1 │ ├── Invoke.ps1 │ ├── ShowPrompt.ps1 │ ├── Silent.ps1 │ ├── get_HiddenStep.ps1 │ └── get_IsComment.ps1 └── Demo │ ├── Demo.format.ps1 │ ├── Dump.ps1 │ ├── NextChapter.ps1 │ ├── NextStep.ps1 │ ├── ProcessInput.ps1 │ ├── Reset.ps1 │ ├── SetChapter.ps1 │ ├── SetStatus.ps1 │ ├── ShowStep.ps1 │ ├── Start.ps1 │ ├── StartChapter.ps1 │ ├── Stop.ps1 │ ├── ToMarkdown.ps1 │ └── get_TotalSteps.ps1 ├── action.yml └── docs ├── 2022-12-04.md ├── 2022-12.md ├── 2022.md ├── 2023-05-23.md ├── 2023-05.md ├── 2023-06-15.md ├── 2023-06.md ├── 2023-08-02.md ├── 2023-08-18.md ├── 2023-08.md ├── 2023-10-10.md ├── 2023-10.md ├── 2023.md ├── 2024-03-09.md ├── 2024-03.md ├── 2024-04-14.md ├── 2024-04.md ├── 2024.md ├── Assets ├── ShowDemo-animated.svg ├── ShowDemo.svg ├── ShowDemo@1080p.png ├── demo.gif └── demo.mp4 ├── CHANGELOG.md ├── CNAME ├── Demo.demo.md ├── Demo ├── NextChapter.md ├── NextStep.md ├── ProcessInput.md ├── README.md ├── Reset.md ├── SetStatus.md ├── Start.md ├── Step │ ├── HidePrompt.md │ ├── Invoke.md │ ├── README.md │ ├── ShowPrompt.md │ └── Silent.md └── Stop.md ├── Export-Demo.md ├── Get-Demo.md ├── Import-Demo.md ├── README.md ├── Resume-Demo.md ├── Show-Demo.md ├── Start-Demo.md ├── Trinity-Of-Discoverability.demo.md ├── _config.yml ├── _posts ├── 2022-12-04-ShowDemo-0.1.md ├── 2022-12-04-Showing-PowerShell-Demos.md ├── 2023-05-23-ShowDemo-0.1.1.md ├── 2023-06-15-ShowDemo-0.1.2.md ├── 2023-08-02-ShowDemo-0.1.3.md ├── 2023-08-18-ShowDemo-0.1.4.md ├── 2023-10-10-ShowDemo-0.1.5.md ├── 2024-03-09-ShowDemo-0.1.6.md └── 2024-04-14-ShowDemo-0.1.7.md └── rss.xml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [StartAutomating] 2 | -------------------------------------------------------------------------------- /.github/workflows/GitPub.yml: -------------------------------------------------------------------------------- 1 | 2 | name: GitPub 3 | on: 4 | workflow_dispatch: 5 | jobs: 6 | RunGitPub: 7 | runs-on: ubuntu-latest 8 | if: ${{ success() }} 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v2 12 | - name: Use GitPub Action 13 | uses: StartAutomating/GitPub@main 14 | id: GitPub 15 | with: 16 | TargetBranch: edits-$([DateTime]::Now.ToString("r").Replace(":","-").Replace(" ", "")) 17 | CommitMessage: Posting with GitPub [skip ci] 18 | PublishParameters: | 19 | { 20 | "Get-GitPubIssue": { 21 | "Repository": '${{github.repository}}' 22 | }, 23 | "Get-GitPubRelease": { 24 | "Repository": '${{github.repository}}' 25 | }, 26 | "Publish-GitPubJekyll": { 27 | "OutputPath": "docs/_posts" 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/SendPSA.yml: -------------------------------------------------------------------------------- 1 | 2 | name: show-demo-psa 3 | on: 4 | workflow_dispatch: 5 | jobs: 6 | SendPSA: 7 | runs-on: ubuntu-latest 8 | if: ${{ success() }} 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | - name: PSA 13 | uses: StartAutomating/PSA@main 14 | id: PSA 15 | env: 16 | AT_PROTOCOL_APP_PASSWORD: ${{ secrets.AT_PROTOCOL_APP_PASSWORD }} 17 | AT_PROTOCOL_HANDLE: mrpowershell.bsky.social 18 | -------------------------------------------------------------------------------- /Assets/ShowDemo-animated.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Show 13 | 14 | Demo 15 | 16 | 17 | -------------------------------------------------------------------------------- /Assets/ShowDemo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Show 12 | Demo 13 | 14 | 15 | -------------------------------------------------------------------------------- /Assets/ShowDemo@1080p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/Assets/ShowDemo@1080p.png -------------------------------------------------------------------------------- /Assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/Assets/demo.gif -------------------------------------------------------------------------------- /Assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/Assets/demo.mp4 -------------------------------------------------------------------------------- /Build/GitHub/Actions/DemoPowerShell.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | GitHub Action for ShowDemo 4 | .Description 5 | GitHub Action for ShowDemo. This will: 6 | 7 | * Import ShowDemo 8 | * Get all demos in the current directory 9 | * Export each demo to a markdown file. 10 | * Run any .ShowDemo.ps1 scripts 11 | * Run the content of the .ShowDemoScript parameter 12 | 13 | Any files changed can be outputted by the script, and those changes can be checked back into the repo. 14 | Make sure to use the "persistCredentials" option with checkout. 15 | #> 16 | 17 | param( 18 | # A PowerShell Script that uses ShowDemo. 19 | # Any files outputted from the script will be added to the repository. 20 | # If those files have a .Message attached to them, they will be committed with that message. 21 | [string] 22 | $ShowDemoScript, 23 | 24 | # If set, will not process any files named *.ShowDemo.ps1 25 | [switch] 26 | $SkipShowDemoPS1, 27 | 28 | # The name of the module for which types and formats are being generated. 29 | # If not provided, this will be assumed to be the name of the root directory. 30 | [string] 31 | $ModuleName, 32 | 33 | # If provided, will commit any remaining changes made to the workspace with this commit message. 34 | [string] 35 | $CommitMessage, 36 | 37 | # The user email associated with a git commit. 38 | [string] 39 | $UserEmail, 40 | 41 | # The user name associated with a git commit. 42 | [string] 43 | $UserName 44 | ) 45 | 46 | #region Initial Logging 47 | 48 | # Output the parameters passed to this script (for debugging) 49 | "::group::Parameters" | Out-Host 50 | [PSCustomObject]$PSBoundParameters | Format-List | Out-Host 51 | "::endgroup::" | Out-Host 52 | 53 | # Get the GitHub Event 54 | $gitHubEvent = 55 | if ($env:GITHUB_EVENT_PATH) { 56 | [IO.File]::ReadAllText($env:GITHUB_EVENT_PATH) | ConvertFrom-Json 57 | } else { $null } 58 | 59 | # Log the GitHub Event 60 | @" 61 | ::group::GitHubEvent 62 | $($gitHubEvent | ConvertTo-Json -Depth 100) 63 | ::endgroup:: 64 | "@ | Out-Host 65 | 66 | # Check that there is a workspace (and throw if there is not) 67 | if (-not $env:GITHUB_WORKSPACE) { throw "No GitHub workspace" } 68 | 69 | #endregion Initial Logging 70 | 71 | # Check to ensure we are on a branch 72 | $branchName = git rev-parse --abrev-ref HEAD 73 | # If we were not, return. 74 | if (-not $branchName) { 75 | "::warning::Not on a branch" | Out-Host 76 | return 77 | } 78 | 79 | #region Configure UserName and Email 80 | if (-not $UserName) { 81 | $UserName = 82 | if ($env:GITHUB_TOKEN) { 83 | Invoke-RestMethod -uri "https://api.github.com/user" -Headers @{ 84 | Authorization = "token $env:GITHUB_TOKEN" 85 | } | 86 | Select-Object -First 1 -ExpandProperty name 87 | } else { 88 | $env:GITHUB_ACTOR 89 | } 90 | } 91 | 92 | if (-not $UserEmail) { 93 | $GitHubUserEmail = 94 | if ($env:GITHUB_TOKEN) { 95 | Invoke-RestMethod -uri "https://api.github.com/user/emails" -Headers @{ 96 | Authorization = "token $env:GITHUB_TOKEN" 97 | } | 98 | Select-Object -First 1 -ExpandProperty email 99 | } else {''} 100 | $UserEmail = 101 | if ($GitHubUserEmail) { 102 | $GitHubUserEmail 103 | } else { 104 | "$UserName@github.com" 105 | } 106 | } 107 | git config --global user.email $UserEmail 108 | git config --global user.name $UserName 109 | #endregion Configure UserName and Email 110 | 111 | 112 | git pull | Out-Host 113 | 114 | 115 | #region Load Action Module 116 | $ActionModuleName = "ShowDemo" 117 | $ActionModuleFileName = "$ActionModuleName.psd1" 118 | 119 | # Try to find a local copy of the action's module. 120 | # This allows the action to use the current branch's code instead of the action's implementation. 121 | $PSD1Found = Get-ChildItem -Recurse -Filter "*.psd1" | 122 | Where-Object Name -eq $ActionModuleFileName | 123 | Select-Object -First 1 124 | 125 | $ActionModulePath, $ActionModule = 126 | # If there was a .PSD1 found 127 | if ($PSD1Found) { 128 | $PSD1Found.FullName # import from there. 129 | Import-Module $PSD1Found.FullName -Force -PassThru 130 | } 131 | # Otherwise, if we have a GITHUB_ACTION_PATH 132 | elseif ($env:GITHUB_ACTION_PATH) 133 | { 134 | $actionModulePath = Join-Path $env:GITHUB_ACTION_PATH $ActionModuleFileName 135 | if (Test-path $actionModulePath) { 136 | $actionModulePath 137 | Import-Module $actionModulePath -Force -PassThru 138 | } else { 139 | throw "$actionModuleName not found" 140 | } 141 | } 142 | elseif (-not (Get-Module $ActionModuleName)) { 143 | throw "$actionModulePath could not be loaded." 144 | } 145 | 146 | "::notice title=ModuleLoaded::$actionModuleName Loaded from Path - $($actionModulePath)" | Out-Host 147 | #endregion Load Action Module 148 | 149 | 150 | #region Install/Import Other Modules 151 | @" 152 | ::group::Installing Modules 153 | $( 154 | "Installing ugit" | Out-Host 155 | Install-Module -Name ugit -Scope CurrentUser -Force 156 | "Importing ugit" | Out-Host 157 | Import-Module ugit -Force -PassThru -Global | Out-Host 158 | ) 159 | ::endgroup:: 160 | "@ | Out-Host 161 | 162 | 163 | #endregion Install Other Modules 164 | 165 | #region Declare Functions and Variables 166 | $anyFilesChanged = $false 167 | filter ProcessScriptOutput { 168 | $out = $_ 169 | $outItem = Get-Item -Path $out -ErrorAction SilentlyContinue 170 | $fullName, $shouldCommit = 171 | if ($out -is [IO.FileInfo]) { 172 | $out.FullName, (git status $out.Fullname -s) 173 | } elseif ($outItem) { 174 | $outItem.FullName, (git status $outItem.Fullname -s) 175 | } 176 | if ($shouldCommit) { 177 | git add $fullName 178 | if ($out.Message) { 179 | git commit -m "$($out.Message)" 180 | } elseif ($out.CommitMessage) { 181 | git commit -m "$($out.CommitMessage)" 182 | } elseif ($out.SourceFile) { 183 | "Source File: $($out.SourceFile)" | Out-Host 184 | $lastCommitMessage = $out.SourceFile | 185 | git log -n 1 | 186 | Select-Object -ExpandProperty CommitMessage 187 | if ($lastCommitMessage) { 188 | git commit -m $lastCommitMessage 189 | } 190 | } elseif ($gitHubEvent.head_commit.message) { 191 | git commit -m "$($gitHubEvent.head_commit.message)" 192 | } 193 | $anyFilesChanged = $true 194 | } 195 | $out 196 | } 197 | 198 | #endregion Declare Functions and Variables 199 | 200 | 201 | #region Actual Action 202 | 203 | $ShowDemoScriptStart = [DateTime]::Now 204 | if ($ShowDemoScript) { 205 | Invoke-Expression -Command $ShowDemoScript | 206 | . processScriptOutput | 207 | Out-Host 208 | } 209 | $ShowDemoScriptTook = [Datetime]::Now - $ShowDemoScriptStart 210 | $ShowDemoPS1Start = [DateTime]::Now 211 | $ShowDemoPS1List = @() 212 | if (-not $SkipShowDemoPS1) { 213 | $ShowDemoFiles = @( 214 | Get-ChildItem -Recurse -Path $env:GITHUB_WORKSPACE | 215 | Where-Object Name -Match '\.ShowDemo\.ps1$') 216 | 217 | if ($ShowDemoFiles) { 218 | $ShowDemoFiles| 219 | ForEach-Object { 220 | $ShowDemoPS1List += $_.FullName.Replace($env:GITHUB_WORKSPACE, '').TrimStart('/') 221 | $ShowDemoPS1Count++ 222 | "::notice title=Running::$($_.Fullname)" | Out-Host 223 | . $_.FullName | 224 | . processScriptOutput | 225 | Out-Host 226 | } 227 | } 228 | } 229 | 230 | "Fetching Changes" | Out-Host 231 | git fetch --unshallow | Out-Host 232 | 233 | #region Export-Demo 234 | "Looking for demos in $env:GITHUB_WORKSPACE" | Out-Host 235 | Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter *.ps1 | 236 | Where-Object Name -Match '(?<=\.|^)(?>demo|walkthru)\.ps1$' | 237 | ForEach-Object { 238 | $demoFile = $_ 239 | $demoFileOut = 240 | $demoFile | 241 | Export-Demo -OutputPath { 242 | $_.FullName -replace '\.ps1$', '.md' 243 | } 244 | 245 | $lastCommitMessage = 246 | git log $demoFile.FullName | 247 | Select-Object -ExpandProperty CommitMessage -First 1 248 | 249 | "LastCommitMessage for $($demoFile.Name): $lastcommitMessage" | Out-Host 250 | $demoFileOut | 251 | Add-Member NoteProperty CommitMessage $lastCommitMessage -Force -PassThru | 252 | . processScriptOutput 253 | } 254 | 255 | 256 | #endregion Export-Demo 257 | 258 | $ShowDemoPS1EndStart = [DateTime]::Now 259 | $ShowDemoPS1Took = [Datetime]::Now - $ShowDemoPS1Start 260 | if ($CommitMessage -or $anyFilesChanged) { 261 | if ($CommitMessage) { 262 | dir $env:GITHUB_WORKSPACE -Recurse | 263 | ForEach-Object { 264 | $gitStatusOutput = git status $_.Fullname -s 265 | if ($gitStatusOutput) { 266 | git add $_.Fullname 267 | } 268 | } 269 | 270 | git commit -m $ExecutionContext.SessionState.InvokeCommand.ExpandString($CommitMessage) 271 | } 272 | 273 | $checkDetached = git symbolic-ref -q HEAD 274 | if (-not $LASTEXITCODE) { 275 | "::notice::Pulling Updates" | Out-Host 276 | git pull 277 | "::notice::Pushing Changes" | Out-Host 278 | git push 279 | "Git Push Output: $($gitPushed | Out-String)" 280 | } else { 281 | "::notice::Not pushing changes (on detached head)" | Out-Host 282 | $LASTEXITCODE = 0 283 | exit 0 284 | } 285 | } 286 | 287 | #endregion Actual Action 288 | 289 | -------------------------------------------------------------------------------- /Build/GitHub/Jobs/BuildShowDemo.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | "runs-on" = "ubuntu-latest" 3 | if = '${{ success() }}' 4 | steps = @( 5 | @{ 6 | name = 'Check out repository' 7 | uses = 'actions/checkout@v3' 8 | }, 9 | @{ 10 | name = 'GitLogger' 11 | uses = 'GitLogging/GitLoggerAction@main' 12 | id = 'GitLogger' 13 | }, 14 | @{ 15 | name = 'Use PSSVG Action' 16 | uses = 'StartAutomating/PSSVG@main' 17 | id = 'PSSVG' 18 | }, 19 | 'RunPipeScript', 20 | 'RunEZOut', 21 | @{ 22 | name = 'Use GitPub Action' 23 | uses = 'StartAutomating/GitPub@main' 24 | id = 'GitPub' 25 | with = @{ 26 | PublishParameters = @' 27 | { 28 | "Get-GitPubIssue": { 29 | "Repository": '${{github.repository}}' 30 | }, 31 | "Get-GitPubRelease": { 32 | "Repository": '${{github.repository}}' 33 | }, 34 | "Publish-GitPubJekyll": { 35 | "OutputPath": "docs/_posts" 36 | } 37 | } 38 | '@ 39 | } 40 | }, 41 | @{ 42 | name = 'Run ShowDemo (on branch)' 43 | if = '${{github.ref_name != ''main''}}' 44 | uses = './' 45 | id = 'ShowDemo' 46 | }, 47 | 'RunHelpOut', 48 | @{ 49 | name = 'PSA' 50 | uses = 'StartAutomating/PSA@main' 51 | id = 'PSA' 52 | } 53 | @{ 54 | 'name'='Log in to the Container registry (on branch)' 55 | 'uses'='docker/login-action@master' 56 | 'with'=@{ 57 | 'registry'='${{ env.REGISTRY }}' 58 | 'username'='${{ github.actor }}' 59 | 'password'='${{ secrets.GITHUB_TOKEN }}' 60 | } 61 | }, 62 | @{ 63 | 'name'='Extract metadata (tags, labels) for Docker' 64 | if = '${{github.ref_name != ''main'' && github.ref_name != ''master'' && github.ref_name != ''latest''}}' 65 | 'id'='meta' 66 | 'uses'='docker/metadata-action@master' 67 | 'with'=@{ 68 | 'images'='${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}' 69 | } 70 | }, 71 | @{ 72 | 'name'='Extract metadata (tags, labels) for Docker' 73 | if = '${{github.ref_name == ''main'' || github.ref_name == ''master'' || github.ref_name == ''latest''}}' 74 | 'id'='metaMain' 75 | 'uses'='docker/metadata-action@master' 76 | 'with'=@{ 77 | 'images'='${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}' 78 | 'flavor'='latest=true' 79 | } 80 | }, 81 | @{ 82 | name = 'Build and push Docker image (from main)' 83 | if = '${{github.ref_name == ''main'' || github.ref_name == ''master'' || github.ref_name == ''latest''}}' 84 | uses = 'docker/build-push-action@master' 85 | 'with'=@{ 86 | 'context'='.' 87 | 'push'='true' 88 | 'tags'='${{ steps.metaMain.outputs.tags }}' 89 | 'labels'='${{ steps.metaMain.outputs.labels }}' 90 | } 91 | }, 92 | @{ 93 | name = 'Build and push Docker image (from branch)' 94 | if = '${{github.ref_name != ''main'' && github.ref_name != ''master'' && github.ref_name != ''latest''}}' 95 | uses = 'docker/build-push-action@master' 96 | with = @{ 97 | 'context'='.' 98 | 'push'='true' 99 | 'tags'='${{ steps.meta.outputs.tags }}' 100 | 'labels'='${{ steps.meta.outputs.labels }}' 101 | } 102 | } 103 | ) 104 | } -------------------------------------------------------------------------------- /Build/GitHub/Jobs/SendPSA.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | "runs-on" = "ubuntu-latest" 3 | if = '${{ success() }}' 4 | steps = @( 5 | @{ 6 | name = 'Check out repository' 7 | uses = 'actions/checkout@v3' 8 | }, 9 | @{ 10 | name = 'PSA' 11 | uses = 'StartAutomating/PSA@main' 12 | id = 'PSA' 13 | } 14 | ) 15 | } -------------------------------------------------------------------------------- /Build/ShowDemo.GitHubAction.PSDevOps.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module PSDevOps 2 | #requires -Module ShowDemo 3 | Import-BuildStep -SourcePath ( 4 | Join-Path $PSScriptRoot 'GitHub' 5 | ) -BuildSystem GitHubWorkflow 6 | 7 | Push-Location ($PSScriptRoot | Split-Path) 8 | New-GitHubAction -Name "DemoPowerShell" -Description @' 9 | Make Demos of your PowerShell projects. 10 | '@ -Action DemoPowerShell -Icon terminal -OutputPath .\action.yml 11 | Pop-Location -------------------------------------------------------------------------------- /Build/ShowDemo.GitHubWorkflow.PSDevOps.ps1: -------------------------------------------------------------------------------- 1 | 2 | #requires -Module PSDevOps 3 | #requires -Module ShowDemo 4 | #requires -Module GitPub 5 | 6 | Import-BuildStep -SourcePath ( 7 | Join-Path $PSScriptRoot 'GitHub' 8 | ) -BuildSystem GitHubWorkflow 9 | 10 | Push-Location ($PSScriptRoot | Split-Path) 11 | 12 | New-GitHubWorkflow -Job PowerShellStaticAnalysis, TestPowerShellOnLinux, TagReleaseAndPublish, BuildShowDemo -OutputPath @' 13 | .\.github\workflows\BuildShowDemo.yml 14 | '@ -Name "Build, Test, and Release ShowDemo" -On Push, PullRequest -Env ([Ordered]@{ 15 | "AT_PROTOCOL_HANDLE" = "mrpowershell.bsky.social" 16 | "AT_PROTOCOL_APP_PASSWORD" = '${{ secrets.AT_PROTOCOL_APP_PASSWORD }}' 17 | "REGISTRY" = "ghcr.io" 18 | "IMAGE_NAME" = '${{ github.repository }}' 19 | }) 20 | 21 | Import-BuildStep -ModuleName GitPub 22 | 23 | New-GitHubWorkflow -On Demand -Job RunGitPub -Name GitPub -OutputPath @' 24 | .\.github\workflows\GitPub.yml 25 | '@ 26 | 27 | New-GitHubWorkflow -On Demand -Name 'show-demo-psa' -Job SendPSA -OutputPath .\.github\workflows\SendPSA.yml -Env @{ 28 | "AT_PROTOCOL_HANDLE" = "mrpowershell.bsky.social" 29 | "AT_PROTOCOL_APP_PASSWORD" = '${{ secrets.AT_PROTOCOL_APP_PASSWORD }}' 30 | } 31 | 32 | Pop-Location 33 | 34 | -------------------------------------------------------------------------------- /Build/ShowDemo.HelpOut.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module HelpOut 2 | Push-Location ($PSScriptRoot | Split-Path) 3 | Import-Module .\ShowDemo.psd1 4 | Save-MarkdownHelp -Module ShowDemo -PassThru 5 | Pop-Location -------------------------------------------------------------------------------- /Build/ShowDemo.PSA.ps1: -------------------------------------------------------------------------------- 1 | # Any *.PSA.ps1 file will be run when PSA runs. 2 | 3 | # A good thing to do at the start of this file is to connect. 4 | 5 | Connect-BlueSky 6 | 7 | # If $env:AT_PROTOCOL_HANDLE or $env:AT_PROTOCOL_EMAIL is set, it will be treated as the username 8 | # If $env:AT_PROTOCOL_APP_PASSWORD is set, it will be treated as the App Password. 9 | # _Never_ use your actual BlueSky password 10 | 11 | # Once we're connected, we can do anything our app password allows. 12 | 13 | # However, you _might_ want to output some information first, so that you can see you're connected. 14 | 15 | Get-BskyActorProfile -Actor $env:AT_PROTOCOL_HANDLE -Cache | Out-Host 16 | 17 | # To ensure you're not going to send a skeet on every checkin, it's a good idea to ask what GitHub is up to 18 | 19 | # There will be a variable, $GitHubEvent, that contains information about the event. 20 | 21 | # A fairly common scenario is to perform an annoucement whenever a PR is merged. 22 | 23 | $isMergeToMain = 24 | ($gitHubEvent.head_commit.message -match "Merge Pull Request #(?\d+)") -and 25 | $gitHubEvent.ref -eq 'refs/heads/main' 26 | 27 | $importedModule = Import-Module .\ShowDemo.psd1 -Global -PassThru 28 | $importedModule | Out-Host 29 | $moduleAndVersion = "$($importedModule.Name) $($importedModule.Version)" 30 | 31 | $isManuallyTriggered = $gitHubEvent.psobject.properties["inputs"] 32 | 33 | if ($isMergeToMain -or $isManuallyTriggered) { 34 | 35 | $fullMessage = @( 36 | "Show off your scripts", 37 | "Demo your PowerShell", 38 | "Never typo during a talk again" | Get-Random 39 | 40 | "$($ImportedModule.Name)" 41 | ) -join [Environment]::NewLine 42 | 43 | $sendSplat = [Ordered]@{ 44 | Text = $fullMessage 45 | } 46 | if ($importedModule.PrivateData.PSData.ProjectURI) { 47 | $sendSplat.WebCard = @{Url=$importedModule.PrivateData.PSData.ProjectURI} 48 | $sendSplat.LinkPattern = @{$importedModule.Name=$importedModule.PrivateData.PSData.ProjectURI} 49 | } 50 | 51 | Send-AtProto @sendSplat 52 | return 53 | } -------------------------------------------------------------------------------- /Build/ShowDemo.PSSVG.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module PSSVG 2 | Push-Location ($psScriptRoot | Split-Path) 3 | $powerShellChevron = Invoke-RestMethod https://pssvg.start-automating.com/Examples/PowerShellChevron.svg 4 | $assetsPath = Join-Path $pwd Assets 5 | $scaleMin = 1 6 | $scaleMax = 1.02 7 | 8 | 9 | $FontSplat = [Ordered]@{ 10 | FontFamily = "sans-serif" 11 | } 12 | 13 | $φ = (1.0 + [Math]::Sqrt(5))/2 14 | 15 | $AnimateSplat = [Ordered]@{ 16 | Dur = 60/128*8 17 | AttributeName = 'font-size' 18 | Values = "${scaleMin}em;${scaleMax}em;${scaleMin}em" 19 | RepeatCount = 'indefinite' 20 | } 21 | 22 | $AnimateSplat2 = [Ordered]@{} + $AnimateSplat 23 | $AnimateSplat2.Values = "${scaleMax}em;${scaleMin}em;${scaleMax}em" 24 | 25 | if (-not (Test-path $assetsPath)) { 26 | $null = New-Item -ItemType Directory -Path $assetsPath -Force 27 | } 28 | 29 | 30 | foreach ($variant in '','animated') { 31 | svg @( 32 | SVG.GoogleFont -FontName $FontName 33 | svg.symbol -ViewBox $powerShellChevron.svg.viewBox -Content $powerShellChevron.svg.symbol.InnerXml -Id psChevron 34 | $RectSplat = [Ordered]@{ 35 | Rx=30 36 | Ry=(30 / $φ) 37 | Stroke="#4488ff" 38 | StrokeWidth="1%" 39 | Fill='transparent' 40 | Width = 250 41 | Height = 250 / $φ 42 | X = 25 43 | Y = 25 / $φ 44 | } 45 | SVG.rect @RectSplat 46 | 47 | svg.use -href '#psChevron' -X '12.5%' -Y '-2%' -Width '12.5%' -Stroke '#4488ff' -Fill '#4488ff' 48 | svg.text @( 49 | svg.tspan "Show" -Children @( 50 | if ($variant -match 'animated') { 51 | SVG.animate @AnimateSplat 52 | } 53 | ) -FontSize "${scaleMin}em" 54 | svg.tspan "Demo" -Children @( 55 | if ($variant -match 'animated') { 56 | SVG.animate @AnimateSplat2 -Begin ($dur / 2) 57 | } 58 | ) -FontSize "${scaleMin}em" 59 | ) -FontSize 32 -Fill '#4488ff' -X 50% -DominantBaseline 'middle' -TextAnchor 'middle' -Y 50% @FontSplat 60 | ) -ViewBox 300, ([Math]::Floor(300 / $φ)) -OutputPath (Join-Path $assetsPath "ShowDemo$(if ($variant){"-$($variant)"}).svg") 61 | } 62 | 63 | Pop-Location 64 | -------------------------------------------------------------------------------- /Build/ShowDemo.ezout.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module EZOut 2 | # Install-Module EZOut or https://github.com/StartAutomating/EZOut 3 | $myFile = $MyInvocation.MyCommand.ScriptBlock.File 4 | $myModuleName = 'ShowDemo' 5 | $myRoot = $myFile | Split-Path | Split-Path 6 | Push-Location $myRoot 7 | $formatting = @( 8 | # Add your own Write-FormatView here, 9 | # or put them in a Formatting or Views directory 10 | foreach ($potentialDirectory in 'Formatting','Views','Types') { 11 | Join-Path $myRoot $potentialDirectory | 12 | Get-ChildItem -ea ignore | 13 | Import-FormatView -FilePath {$_.Fullname} 14 | } 15 | ) 16 | 17 | $destinationRoot = $myRoot 18 | 19 | if ($formatting) { 20 | $myFormatFile = Join-Path $destinationRoot "$myModuleName.format.ps1xml" 21 | $formatting | Out-FormatData -Module $MyModuleName | Set-Content $myFormatFile -Encoding UTF8 22 | Get-Item $myFormatFile 23 | } 24 | 25 | $types = @( 26 | # Add your own Write-TypeView statements here 27 | # or declare them in the 'Types' directory 28 | Join-Path $myRoot Types | 29 | Get-Item -ea ignore | 30 | Import-TypeView 31 | 32 | ) 33 | 34 | if ($types) { 35 | $myTypesFile = Join-Path $destinationRoot "$myModuleName.types.ps1xml" 36 | $types | Out-TypeData | Set-Content $myTypesFile -Encoding UTF8 37 | Get-Item $myTypesFile 38 | } 39 | Pop-Location 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | > Like It? [Star It](https://github.com/StartAutomating/ShowDemo) 2 | > Love It? [Support It](https://github.com/sponsors/StartAutomating) 3 | 4 | --- 5 | 6 | ## ShowDemo 0.1.7: 7 | 8 | * ShowDemo in Docker (#103) 9 | * Added Dockerfile (#104) 10 | * Publishing all builds to GitHub Container Registry (#105) 11 | * Added Trinity of Discoverability Demo (#51) 12 | * Exporting $ShowDemo (#106) 13 | * Mounting as ShowDemo: (#107) 14 | 15 | --- 16 | 17 | ## ShowDemo 0.1.6: 18 | 19 | * Show-Demo Syncing Console Encoding ( Fixes #101 ) 20 | * Show-Demo -PauseBetweenLine(s) ( Fixes #100 ) 21 | * Adjusting Default Type Speed ( Fixes #97 ) 22 | * Showing unknown steps in White, not Output ( Fixes #99 ) 23 | 24 | --- 25 | 26 | ## ShowDemo 0.1.5: 27 | 28 | * Demos are now more eventful (#66) 29 | * Nearly every part of ShowDemo transmits PowerShell engine events 30 | * These can be used for highly customized display of demos 31 | * Refactoring all *-Demo commands to use a single -From parameter (#86) 32 | * Added Logo (#90) 33 | * Integrated PSA (#91) 34 | 35 | --- 36 | 37 | ## ShowDemo 0.1.4: 38 | 39 | * ShowDemo - Adding Recommendations (Fixes #63) 40 | * Demo Format - Honoring .StartMessage/.EndMessage (Fixes #62) 41 | * Show-Demo - Adding -StartMessage/-EndMessage (Fixes #61) 42 | 43 | --- 44 | 45 | ## ShowDemo 0.1.3: 46 | 47 | * Adding support for prompts in demos 48 | * Demo.Step - Adding .ShowPrompt()/HidePrompt() (#54/#55) 49 | * Demo Formatting - Supporting ShowPrompt (#56) 50 | * Show-Demo - Adding -ShowPrompt (#53) 51 | * Import-Demo - Linking Chapters (#57) 52 | * Partitioning repository (#48, #49, #50) 53 | 54 | --- 55 | 56 | ## ShowDemo 0.1.2: 57 | 58 | * Get-Demo - Skipping $pwd if in $filePaths (Fixes #43) 59 | * Show-Demo - Adding -Record (Fixes #42) 60 | * Import-Demo - Including .DemoScript (Fixes #44) 61 | * Adding Demo.ToMarkdown (Fixes #45) 62 | 63 | --- 64 | 65 | ## ShowDemo 0.1.1 66 | 67 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep (#39) 68 | * Export-Demo - Defaults to English when invariant culture (Fixes #37) 69 | * Improvements in how steps are determined (#35 #36) 70 | * Please Sponsor ShowDemo (#38) 71 | 72 | --- 73 | 74 | ## ShowDemo 0.1 75 | 76 | Initial Release of Show-Demo. 77 | 78 | * List Demos with Get-Demo 79 | * Show Demos with Show-Demo 80 | * Export Demos with Export-Demo. 81 | * ShowDemo GitHub Action 82 | 83 | -------------------------------------------------------------------------------- /Commands/Export-Demo.ps1: -------------------------------------------------------------------------------- 1 | function Export-Demo 2 | { 3 | <# 4 | .SYNOPSIS 5 | Exports Demos 6 | .DESCRIPTION 7 | Exports a Demo. 8 | 9 | Demos can be saved to a Markdown (.md) file or a Clixml (.clixml/.clix)file 10 | .EXAMPLE 11 | Export-Demo -DemoPath .\demo.ps1 -OutputPath .\demo.md 12 | #> 13 | param( 14 | # The source of the demo. This can be a string, file, command, module, or path. 15 | [Parameter(Mandatory, 16 | ValueFromPipelineByPropertyName, 17 | ParameterSetName='DemoFile')] 18 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 19 | [PSObject] 20 | $From, 21 | 22 | # The output path. This is the location the demo will be saved to. 23 | [Parameter(Mandatory,ValueFromPipelineByPropertyName)] 24 | [ValidatePattern('\.(?>clixml|clix|md)$')] 25 | [Alias('OutputFile')] 26 | [string] 27 | $OutputPath 28 | ) 29 | 30 | begin { 31 | $extensionPattern = '\.(?>ps1|clixml|clix|md|html)$' 32 | 33 | # In order to make Markdown demos render correctly, we need to override Write-Host 34 | # (in case the demo Writes to the host) 35 | function Write-Host { 36 | <# 37 | .ForwardHelpTargetName Write-Host 38 | .ForwardHelpCategory Cmdlet 39 | #> 40 | 41 | [CmdletBinding()] 42 | [OutputType([Nullable])] 43 | param( 44 | [Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)] 45 | [System.Object] 46 | ${Object}, 47 | 48 | [Switch] 49 | ${NoNewline}, 50 | 51 | [System.Object] 52 | ${Separator}, 53 | 54 | [System.ConsoleColor] 55 | ${ForegroundColor}, 56 | 57 | [System.ConsoleColor] 58 | ${BackgroundColor} 59 | ) 60 | process { 61 | #region Override Write-Host for inside of the demo 62 | if ($demo) { 63 | # Write as HTML 64 | $objectHtml = $Object 65 | 66 | $styleChunk = if ($ForegroundColor -or $backgroundColor) { 67 | if ($ForegroundColor -and $backgroundcolor) { 68 | " style='color:${ForeGroundColor};background=${BackgroundColor}'" 69 | } else { 70 | if ($ForegroundColor) { 71 | " style='color:${ForeGroundColor}'" 72 | } else { 73 | " style='background=${BackgroundColor}'" 74 | } 75 | } 76 | } else { 77 | "" 78 | } 79 | $tag = "span" 80 | "<${tag}${styleChunk}>${ObjectHtml}$(if (-not $NoNewLine) {'
'})" 81 | } else { 82 | # If we're not in a web site... 83 | Microsoft.PowerShell.Utility\Write-Host @psboundParameters 84 | } 85 | #endregion Override Write-Host for web context 86 | } 87 | } 88 | 89 | } 90 | process { 91 | $demoContents = Get-Demo -From $From 92 | if (-not $demoContents) { return } 93 | 94 | if ($OutputPath -notmatch $extensionPattern) { 95 | Write-Error "Invalid Extension" 96 | return 97 | } 98 | $extension = $matches.0 99 | $unresolvedOutput = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) 100 | if ($extension -in '.clixml', '.clix') { 101 | Export-Clixml -LiteralPath $unresolvedOutput -InputObject $demoContents 102 | 103 | Get-Item -LiteralPath $unresolvedOutput | 104 | Add-Member NoteProperty SourceFile $demoContents.DemoFile -Force -PassThru 105 | } 106 | elseif ($extension -eq '.md') { 107 | if ([Globalization.CultureInfo]::CurrentCulture.LCID -eq 127) { 108 | [Globalization.CultureInfo]::CurrentCulture = 'en-us' 109 | } 110 | $demoContents | Add-Member NoteProperty Markdown $true -Force 111 | $demoContents | Add-Member NoteProperty Interactive $false -Force 112 | $demoContents | 113 | Format-Custom | 114 | Out-String -Width 1mb | 115 | Set-Content -LiteralPath $unresolvedOutput -Encoding UTF8 116 | 117 | Get-Item -LiteralPath $unresolvedOutput | 118 | Add-Member NoteProperty SourceFile $demoContents.DemoFile -Force -PassThru 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Commands/Get-Demo.ps.ps1: -------------------------------------------------------------------------------- 1 | function Get-Demo 2 | { 3 | <# 4 | .SYNOPSIS 5 | Gets Demos 6 | .DESCRIPTION 7 | Gets PowerShell Demos. 8 | 9 | Demos located in ShowDemo and all modules that tag ShowDemo will be automatically discovered. 10 | .LINK 11 | Import-Demo 12 | .EXAMPLE 13 | Get-Demo 14 | #> 15 | [CmdletBinding(DefaultParameterSetName='LoadedDemos')] 16 | param( 17 | # The source of the demo. This can be a string, file, command, module, or path. 18 | [vbn(Mandatory,ParameterSetName='DemoFile')] 19 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 20 | [PSObject] 21 | $From 22 | ) 23 | 24 | begin { 25 | # If we have not initialized a cache and we're inside of a module 26 | if (-not $script:CachedDemos.Count -and $MyInvocation.MyCommand.ScriptBlock.Module) { 27 | $MyModule = $MyInvocation.MyCommand.ScriptBlock.Module 28 | Import-Demo -From $MyModule | Out-Null 29 | } 30 | } 31 | 32 | process { 33 | if ($from) { 34 | Import-Demo -From $from 35 | } elseif ($script:CachedDemos.Count) { 36 | $script:CachedDemos.Values 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Commands/Get-Demo.ps1: -------------------------------------------------------------------------------- 1 | function Get-Demo { 2 | 3 | <# 4 | .SYNOPSIS 5 | Gets Demos 6 | .DESCRIPTION 7 | Gets PowerShell Demos. 8 | 9 | Demos located in ShowDemo and all modules that tag ShowDemo will be automatically discovered. 10 | .LINK 11 | Import-Demo 12 | .EXAMPLE 13 | Get-Demo 14 | #> 15 | [CmdletBinding(DefaultParameterSetName='LoadedDemos')] 16 | param( 17 | # The source of the demo. This can be a string, file, command, module, or path. 18 | [Parameter(Mandatory,ParameterSetName='DemoFile',ValueFromPipelineByPropertyName)] 19 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 20 | [PSObject] 21 | $From 22 | ) 23 | 24 | begin { 25 | # If we have not initialized a cache and we're inside of a module 26 | if (-not $script:CachedDemos.Count -and $MyInvocation.MyCommand.ScriptBlock.Module) { 27 | $MyModule = $MyInvocation.MyCommand.ScriptBlock.Module 28 | Import-Demo -From $MyModule | Out-Null 29 | } 30 | } 31 | 32 | process { 33 | if ($from) { 34 | Import-Demo -From $from 35 | } elseif ($script:CachedDemos.Count) { 36 | $script:CachedDemos.Values 37 | } 38 | } 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Commands/Import-Demo.ps.ps1: -------------------------------------------------------------------------------- 1 | function Import-Demo 2 | { 3 | <# 4 | .SYNOPSIS 5 | Imports Demos 6 | .DESCRIPTION 7 | Imports a Demo script. 8 | .LINK 9 | Export-Demo 10 | .LINK 11 | Get-Demo 12 | .LINK 13 | Start-Demo 14 | .EXAMPLE 15 | Import-Demo -DemoName "Demo" 16 | #> 17 | param( 18 | # The source of the demo. This can be a string, file, command, module, or path. 19 | [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName='DemoFile')] 20 | [ValidateTypes(TypeName={ 21 | [IO.FileInfo] 22 | [IO.DirectoryInfo] 23 | [Management.Automation.PathInfo] 24 | [Management.Automation.CommandInfo] 25 | [Management.Automation.PSModuleInfo] 26 | [string] 27 | })] 28 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 29 | [PSObject] 30 | $From 31 | ) 32 | 33 | begin { 34 | $ChapterExpression = '^\s{0,}(?(?:\d+\.){1,})\s{0,}' 35 | $ValidDemoFile = [regex]::new('\.demo\.(?>ps1|clixml)$','IgnoreCase') 36 | if (-not $script:CachedDemos) { 37 | $script:CachedDemos = [Ordered]@{} 38 | } 39 | # If we have not initialized a cache and we're inside of a module 40 | if (-not $script:CachedDemos.Count -and $MyInvocation.MyCommand.ScriptBlock.Module) { 41 | # we'll need to import our own demos. 42 | # to ensure this only happens once, 43 | $MyCmd = $MyInvocation.MyCommand 44 | $myDepth = 0 45 | foreach ($frame in Get-PSCallStack) { # we callstack peek 46 | if ($frame.InvocationInfo.MyCommand.Name -eq "$MyCmd") { 47 | $myDepth++ # and track our depth 48 | } 49 | } 50 | if ($myDepth -eq 1) { # and only import if we are one level deep. 51 | $MyModule = $MyInvocation.MyCommand.ScriptBlock.Module 52 | Import-Demo -From $MyModule | Out-Null 53 | } 54 | } 55 | 56 | # Next we declare a few internal functions. 57 | # Essentially, our import will boil down to a few possible scenarios: 58 | 59 | # The easy one is Importing a demo .clixml 60 | function Import-DemoClixml { 61 | if ($FromFileInfo -match '\.(clix|clixml)$') { 62 | return Import-Clixml -LiteralPath $FromFileInfo.FullName 63 | } 64 | } 65 | # The big one is Importing a demo from a script 66 | function Import-DemoScript { 67 | if (-not $FromScriptBlock) { 68 | return 69 | } 70 | 71 | $demoName = 72 | if ($FromCommand) { 73 | $FromCommand.Name -replace $ValidDemoFile 74 | } elseif ($FromFileInfo) { 75 | $FromFileInfo.Name -replace $ValidDemoFile 76 | } 77 | 78 | $astString = "$from" 79 | $psTokens = [Management.Automation.PSParser]::Tokenize($astString, [ref]$null) 80 | if (-not $psTokens) { return } 81 | 82 | $chapters = @() 83 | $currentChapter = $null 84 | $chapterTokens = @() 85 | 86 | # We want every step to be able to run independently. 87 | # This would be untrue if the code is unbalanced when a chapter would start 88 | # Thus, while we're primarily looking for comments, we also need to track groups 89 | $groupDepth = 0 90 | $previousToken = $null 91 | # Walk thru every token in the file. 92 | foreach ($token in $psTokens) { 93 | Add-Member NoteProperty PreviousToken $previousToken -Force -InputObject $token 94 | Add-Member NoteProperty Text $astString -Force -InputObject $token 95 | $previousToken = $token 96 | if ($token.Type -in 'Variable', 'String') { 97 | $realContent = $astString.Substring($token.Start, $token.Length) 98 | Add-Member NoteProperty Content $realContent -Force -InputObject $token 99 | } 100 | # If the token is a group start 101 | if ($token.Type -eq 'GroupStart') 102 | { 103 | $groupDepth++ # increment depth. 104 | } 105 | # If the token was a group end 106 | elseif ($token.Type -eq 'GroupEnd') 107 | { 108 | $groupDepth-- # decrement depth. 109 | } 110 | # If there was no depth 111 | # and the token was a comment starting in the first column. 112 | elseif ( 113 | (-not $groupDepth) -and 114 | $token.Type -eq 'Comment' -and $token.StartColumn -le 1 115 | ) 116 | { 117 | $tokenContent = $token.Content -replace '^#' -replace '#$' 118 | # Then it could be the start of a chapter. 119 | 120 | # If it is not, 121 | if ($tokenContent -notmatch $ChapterExpression) { 122 | $chapterTokens += $token # add it to the current chapter. 123 | } 124 | 125 | # If the comment does start a chapter 126 | else { 127 | # get the chapter number from `$matches`. 128 | $chapterNumber = $matches.cn 129 | # Then get the chapter name by replacing the regex. 130 | $chapterName = $tokenContent -replace $ChapterExpression 131 | 132 | # Create a new chapter, starting at the current token. 133 | $newChapter = [Ordered]@{ 134 | Number = $chapterNumber 135 | Name = $chapterName 136 | Text = $astString 137 | Start = $token.Start 138 | DemoFile = $FromFileInfo.FullName 139 | } 140 | 141 | # If there was already a current chapter 142 | if ($currentChapter) { 143 | # finalize it by marking it's end 144 | $currentChapter.Length = 145 | $chapterTokens[-1].Start + $chapterTokens[-1].End - $currentChapter.Start 146 | # and attaching the tokens we have so far. 147 | $currentChapter.Tokens = $chapterTokens 148 | $chapterTokens = @() 149 | $chapters += $currentChapter 150 | } 151 | 152 | # Then, make the new chapter the current chapter 153 | $currentChapter = $newChapter 154 | } 155 | } 156 | else 157 | { 158 | $chapterTokens += $token 159 | } 160 | } 161 | 162 | # If we have a named chapter 163 | if ($currentChapter) { 164 | # add any remaining tokens to it. 165 | $currentChapter.Tokens = $chapterTokens 166 | $chapterTokens = @() 167 | # and add the chapter. 168 | $chapters += $currentChapter 169 | } elseif ($chapterTokens) { 170 | # Otherwise, if we have chapter tokens by no named chapter 171 | # create an empty chapter and add the steps to it. 172 | $chapters += [Ordered]@{ 173 | Number = '' 174 | Name = '' 175 | Tokens = $chapterTokens 176 | Text = $astString 177 | } 178 | } 179 | 180 | $demoScript = [scriptblock]::create($astString) 181 | 182 | # Create the demo object. 183 | $demoFile = [PSCustomObject][Ordered]@{ 184 | PSTypeName = 'Demo' 185 | Name = $demoName 186 | DemoFile = $fileInfo.FullName 187 | DemoScript = $DemoScript 188 | DemoText = "$DemoScript" 189 | } 190 | 191 | 192 | if ($demoName) { 193 | $script:CachedDemos[$demoName] = $demoFile 194 | } 195 | 196 | # And add each chapter to it 197 | $demoFile | 198 | Add-Member NoteProperty Chapters @( 199 | foreach ($chapter in $chapters) { 200 | # linking back to the demo as we go (#57). 201 | [PSCustomObject]([Ordered]@{PSTypeName='Demo.Chapter';Demo=$demoFile} + $chapter) 202 | } 203 | ) -Force -PassThru # We -PassThru the modified object to return it. 204 | } 205 | } 206 | 207 | process { 208 | # Since -From can be many things, but a demo has to be a ScriptBlock, 209 | # the purpose of this function is to essentially resolve many things to one or more ScriptBlocks. 210 | $fromModule, $FromDirectory, $FromCommand, $FromFileInfo, $fromScriptBlock = $null, $null,$null, $null, $null 211 | 212 | # If -From was a string 213 | if ($From -is [string]) { 214 | # It could be a module, so check those first. 215 | :ResolveFromString do { 216 | if ($from -match '\n') { 217 | # If the -From had newlines, try to make a [ScriptBlock] 218 | $fromScriptBlock = try { [ScriptBlock]::create($from) } catch { } 219 | if ($fromScriptBlock) { break } 220 | } 221 | 222 | foreach ($loadedModule in @(Get-Module)) { 223 | # If we find the module, don't try to resolve -From as a path 224 | break ResolveFromString 225 | if ($loadedModule.Name -eq $from) { 226 | # (just set -From again and let the function continue) 227 | $from = $fromModule = $loadedModule 228 | } 229 | } 230 | if ($script:CachedDemos["$From"]) { 231 | $script:CachedDemos["$From"] 232 | } else { 233 | # If we think from was a path 234 | foreach ($resolvedPath in $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($from)) { 235 | Import-Demo -From $resolvedPath 236 | } 237 | } 238 | return 239 | } while ($false) 240 | } 241 | 242 | if ($From -is [Management.Automation.PSModuleInfo]) { 243 | # then, make -From a directory 244 | if ($from.Path) { 245 | $from = Get-Item ($from.Path | Split-Path) -ErrorAction SilentlyContinue 246 | } 247 | } 248 | 249 | # If -From is a path 250 | if ($from -is [Management.Automation.PathInfo]) { 251 | $from = Get-Item -LiteralPath "$from" # turn it into a file or a directory 252 | } 253 | 254 | # If -From is a directory 255 | if ($from -is [IO.DirectoryInfo]) { 256 | $FromDirectory = $from 257 | # recursively call ourselves with all matching scripts 258 | Get-ChildItem -LiteralPath $from.FullName -Recurse -File | 259 | Where-Object Name -match $ValidDemoFile | 260 | Import-Demo 261 | return 262 | } 263 | 264 | # If -From is a file 265 | if ($from -is [IO.FileInfo]) { 266 | # set $FromFileInfo 267 | $FromFileInfo = $from 268 | # and make -From a command. 269 | if ($from.Extension -eq '.ps1') { 270 | $from = $ExecutionContext.SessionState.InvokeCommand.GetCommand($from.FullName, 'ExternalScript') 271 | } 272 | } 273 | 274 | # If -From is a command 275 | if ($from -is [Management.Automation.CommandInfo]) { 276 | $FromCommand = $From 277 | # and it is an external script 278 | if ($from -is [Management.Automation.ExternalScriptInfo]) { 279 | # then use that script's contents as the source 280 | $FromScriptBlock = $from.ScriptBlock 281 | if (-not $FromFileInfo) { 282 | $FromFileInfo = [IO.FileInfo]$from.Source 283 | } 284 | $From = "$FromScriptBlock" # (stringified) 285 | } 286 | else { 287 | 288 | # Otherwise, see if it has help and examples 289 | $cmdHelp = Get-Help $from -ErrorAction Ignore 290 | if ($cmdHelp -and $cmdHelp.examples.example) { 291 | # and make the demo the combined sequence of examples 292 | $allExampleLines = @( 293 | foreach ($example in $cmdHelp.Examples.example) { 294 | $example.Code 295 | foreach ($remark in $example.Remarks.text) { 296 | if (-not $remark) { continue } 297 | $remark 298 | } 299 | } 300 | ) -join [Environment]::NewLine 301 | try { 302 | # at least, assuming we can convert it into a [ScriptBlock] 303 | $From = $fromScriptBlock = [scriptblock]::create($allExampleLines) 304 | } catch { 305 | $ex = $_ 306 | Write-Error "Example in $from cannot be converted into a ScriptBlock" 307 | return 308 | } 309 | } 310 | } 311 | } 312 | 313 | 314 | if ($fromScriptBlock) { 315 | . Import-DemoScript 316 | } 317 | elseif ($FromFileInfo) { 318 | . Import-DemoClixml 319 | } 320 | } 321 | } 322 | 323 | -------------------------------------------------------------------------------- /Commands/Import-Demo.ps1: -------------------------------------------------------------------------------- 1 | function Import-Demo { 2 | 3 | <# 4 | .SYNOPSIS 5 | Imports Demos 6 | .DESCRIPTION 7 | Imports a Demo script. 8 | .LINK 9 | Export-Demo 10 | .LINK 11 | Get-Demo 12 | .LINK 13 | Start-Demo 14 | .EXAMPLE 15 | Import-Demo -DemoName "Demo" 16 | #> 17 | param( 18 | # The source of the demo. This can be a string, file, command, module, or path. 19 | [Parameter(Mandatory,ValueFromPipelineByPropertyName,ParameterSetName='DemoFile')] 20 | [ValidateScript({ 21 | $validTypeList = [System.IO.FileInfo],[System.IO.DirectoryInfo],[System.Management.Automation.PathInfo],[System.Management.Automation.CommandInfo],[System.Management.Automation.PSModuleInfo],[System.String] 22 | 23 | $thisType = $_.GetType() 24 | $IsTypeOk = 25 | $(@( foreach ($validType in $validTypeList) { 26 | if ($_ -as $validType) { 27 | $true;break 28 | } 29 | })) 30 | 31 | if (-not $isTypeOk) { 32 | throw "Unexpected type '$(@($thisType)[0])'. Must be 'System.IO.FileInfo','System.IO.DirectoryInfo','System.Management.Automation.PathInfo','System.Management.Automation.CommandInfo','psmoduleinfo','string'." 33 | } 34 | return $true 35 | })] 36 | 37 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 38 | [PSObject] 39 | $From 40 | ) 41 | 42 | begin { 43 | $ChapterExpression = '^\s{0,}(?(?:\d+\.){1,})\s{0,}' 44 | $ValidDemoFile = [regex]::new('\.demo\.(?>ps1|clixml)$','IgnoreCase') 45 | if (-not $script:CachedDemos) { 46 | $script:CachedDemos = [Ordered]@{} 47 | } 48 | # If we have not initialized a cache and we're inside of a module 49 | if (-not $script:CachedDemos.Count -and $MyInvocation.MyCommand.ScriptBlock.Module) { 50 | # we'll need to import our own demos. 51 | # to ensure this only happens once, 52 | $MyCmd = $MyInvocation.MyCommand 53 | $myDepth = 0 54 | foreach ($frame in Get-PSCallStack) { # we callstack peek 55 | if ($frame.InvocationInfo.MyCommand.Name -eq "$MyCmd") { 56 | $myDepth++ # and track our depth 57 | } 58 | } 59 | if ($myDepth -eq 1) { # and only import if we are one level deep. 60 | $MyModule = $MyInvocation.MyCommand.ScriptBlock.Module 61 | Import-Demo -From $MyModule | Out-Null 62 | } 63 | } 64 | 65 | # Next we declare a few internal functions. 66 | # Essentially, our import will boil down to a few possible scenarios: 67 | 68 | # The easy one is Importing a demo .clixml 69 | function Import-DemoClixml { 70 | 71 | if ($FromFileInfo -match '\.(clix|clixml)$') { 72 | return Import-Clixml -LiteralPath $FromFileInfo.FullName 73 | } 74 | 75 | } 76 | # The big one is Importing a demo from a script 77 | function Import-DemoScript { 78 | 79 | if (-not $FromScriptBlock) { 80 | return 81 | } 82 | 83 | $demoName = 84 | if ($FromCommand) { 85 | $FromCommand.Name -replace $ValidDemoFile 86 | } elseif ($FromFileInfo) { 87 | $FromFileInfo.Name -replace $ValidDemoFile 88 | } 89 | 90 | $astString = "$from" 91 | $psTokens = [Management.Automation.PSParser]::Tokenize($astString, [ref]$null) 92 | if (-not $psTokens) { return } 93 | 94 | $chapters = @() 95 | $currentChapter = $null 96 | $chapterTokens = @() 97 | 98 | # We want every step to be able to run independently. 99 | # This would be untrue if the code is unbalanced when a chapter would start 100 | # Thus, while we're primarily looking for comments, we also need to track groups 101 | $groupDepth = 0 102 | $previousToken = $null 103 | # Walk thru every token in the file. 104 | foreach ($token in $psTokens) { 105 | Add-Member NoteProperty PreviousToken $previousToken -Force -InputObject $token 106 | Add-Member NoteProperty Text $astString -Force -InputObject $token 107 | $previousToken = $token 108 | if ($token.Type -in 'Variable', 'String') { 109 | $realContent = $astString.Substring($token.Start, $token.Length) 110 | Add-Member NoteProperty Content $realContent -Force -InputObject $token 111 | } 112 | # If the token is a group start 113 | if ($token.Type -eq 'GroupStart') 114 | { 115 | $groupDepth++ # increment depth. 116 | } 117 | # If the token was a group end 118 | elseif ($token.Type -eq 'GroupEnd') 119 | { 120 | $groupDepth-- # decrement depth. 121 | } 122 | # If there was no depth 123 | # and the token was a comment starting in the first column. 124 | elseif ( 125 | (-not $groupDepth) -and 126 | $token.Type -eq 'Comment' -and $token.StartColumn -le 1 127 | ) 128 | { 129 | $tokenContent = $token.Content -replace '^#' -replace '#$' 130 | # Then it could be the start of a chapter. 131 | 132 | # If it is not, 133 | if ($tokenContent -notmatch $ChapterExpression) { 134 | $chapterTokens += $token # add it to the current chapter. 135 | } 136 | 137 | # If the comment does start a chapter 138 | else { 139 | # get the chapter number from `$matches`. 140 | $chapterNumber = $matches.cn 141 | # Then get the chapter name by replacing the regex. 142 | $chapterName = $tokenContent -replace $ChapterExpression 143 | 144 | # Create a new chapter, starting at the current token. 145 | $newChapter = [Ordered]@{ 146 | Number = $chapterNumber 147 | Name = $chapterName 148 | Text = $astString 149 | Start = $token.Start 150 | DemoFile = $FromFileInfo.FullName 151 | } 152 | 153 | # If there was already a current chapter 154 | if ($currentChapter) { 155 | # finalize it by marking it's end 156 | $currentChapter.Length = 157 | $chapterTokens[-1].Start + $chapterTokens[-1].End - $currentChapter.Start 158 | # and attaching the tokens we have so far. 159 | $currentChapter.Tokens = $chapterTokens 160 | $chapterTokens = @() 161 | $chapters += $currentChapter 162 | } 163 | 164 | # Then, make the new chapter the current chapter 165 | $currentChapter = $newChapter 166 | } 167 | } 168 | else 169 | { 170 | $chapterTokens += $token 171 | } 172 | } 173 | 174 | # If we have a named chapter 175 | if ($currentChapter) { 176 | # add any remaining tokens to it. 177 | $currentChapter.Tokens = $chapterTokens 178 | $chapterTokens = @() 179 | # and add the chapter. 180 | $chapters += $currentChapter 181 | } elseif ($chapterTokens) { 182 | # Otherwise, if we have chapter tokens by no named chapter 183 | # create an empty chapter and add the steps to it. 184 | $chapters += [Ordered]@{ 185 | Number = '' 186 | Name = '' 187 | Tokens = $chapterTokens 188 | Text = $astString 189 | } 190 | } 191 | 192 | $demoScript = [scriptblock]::create($astString) 193 | 194 | # Create the demo object. 195 | $demoFile = [PSCustomObject][Ordered]@{ 196 | PSTypeName = 'Demo' 197 | Name = $demoName 198 | DemoFile = $fileInfo.FullName 199 | DemoScript = $DemoScript 200 | DemoText = "$DemoScript" 201 | } 202 | 203 | 204 | if ($demoName) { 205 | $script:CachedDemos[$demoName] = $demoFile 206 | } 207 | 208 | # And add each chapter to it 209 | $demoFile | 210 | Add-Member NoteProperty Chapters @( 211 | foreach ($chapter in $chapters) { 212 | # linking back to the demo as we go (#57). 213 | [PSCustomObject]([Ordered]@{PSTypeName='Demo.Chapter';Demo=$demoFile} + $chapter) 214 | } 215 | ) -Force -PassThru # We -PassThru the modified object to return it. 216 | 217 | } 218 | } 219 | 220 | process { 221 | # Since -From can be many things, but a demo has to be a ScriptBlock, 222 | # the purpose of this function is to essentially resolve many things to one or more ScriptBlocks. 223 | $fromModule, $FromDirectory, $FromCommand, $FromFileInfo, $fromScriptBlock = $null, $null,$null, $null, $null 224 | 225 | # If -From was a string 226 | if ($From -is [string]) { 227 | # It could be a module, so check those first. 228 | :ResolveFromString do { 229 | if ($from -match '\n') { 230 | # If the -From had newlines, try to make a [ScriptBlock] 231 | $fromScriptBlock = try { [ScriptBlock]::create($from) } catch { } 232 | if ($fromScriptBlock) { break } 233 | } 234 | 235 | foreach ($loadedModule in @(Get-Module)) { 236 | # If we find the module, don't try to resolve -From as a path 237 | if ($loadedModule.Name -eq $from) { 238 | # (just set -From again and let the function continue) 239 | $from = $fromModule = $loadedModule;break ResolveFromString 240 | } 241 | 242 | } 243 | if ($script:CachedDemos["$From"]) { 244 | $script:CachedDemos["$From"] 245 | } else { 246 | # If we think from was a path 247 | foreach ($resolvedPath in $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($from)) { 248 | Import-Demo -From $resolvedPath 249 | } 250 | } 251 | return 252 | } while ($false) 253 | } 254 | 255 | if ($From -is [Management.Automation.PSModuleInfo]) { 256 | # then, make -From a directory 257 | if ($from.Path) { 258 | $from = Get-Item ($from.Path | Split-Path) -ErrorAction SilentlyContinue 259 | } 260 | } 261 | 262 | # If -From is a path 263 | if ($from -is [Management.Automation.PathInfo]) { 264 | $from = Get-Item -LiteralPath "$from" # turn it into a file or a directory 265 | } 266 | 267 | # If -From is a directory 268 | if ($from -is [IO.DirectoryInfo]) { 269 | $FromDirectory = $from 270 | # recursively call ourselves with all matching scripts 271 | Get-ChildItem -LiteralPath $from.FullName -Recurse -File | 272 | Where-Object Name -match $ValidDemoFile | 273 | Import-Demo 274 | return 275 | } 276 | 277 | # If -From is a file 278 | if ($from -is [IO.FileInfo]) { 279 | # set $FromFileInfo 280 | $FromFileInfo = $from 281 | # and make -From a command. 282 | if ($from.Extension -eq '.ps1') { 283 | $from = $ExecutionContext.SessionState.InvokeCommand.GetCommand($from.FullName, 'ExternalScript') 284 | } 285 | } 286 | 287 | # If -From is a command 288 | if ($from -is [Management.Automation.CommandInfo]) { 289 | $FromCommand = $From 290 | # and it is an external script 291 | if ($from -is [Management.Automation.ExternalScriptInfo]) { 292 | # then use that script's contents as the source 293 | $FromScriptBlock = $from.ScriptBlock 294 | if (-not $FromFileInfo) { 295 | $FromFileInfo = [IO.FileInfo]$from.Source 296 | } 297 | $From = "$FromScriptBlock" # (stringified) 298 | } 299 | else { 300 | 301 | # Otherwise, see if it has help and examples 302 | $cmdHelp = Get-Help $from -ErrorAction Ignore 303 | if ($cmdHelp -and $cmdHelp.examples.example) { 304 | # and make the demo the combined sequence of examples 305 | $allExampleLines = @( 306 | foreach ($example in $cmdHelp.Examples.example) { 307 | $example.Code 308 | foreach ($remark in $example.Remarks.text) { 309 | if (-not $remark) { continue } 310 | $remark 311 | } 312 | } 313 | ) -join [Environment]::NewLine 314 | try { 315 | # at least, assuming we can convert it into a [ScriptBlock] 316 | $From = $fromScriptBlock = [scriptblock]::create($allExampleLines) 317 | } catch { 318 | $ex = $_ 319 | Write-Error "Example in $from cannot be converted into a ScriptBlock" 320 | return 321 | } 322 | } 323 | } 324 | } 325 | 326 | 327 | if ($fromScriptBlock) { 328 | . Import-DemoScript 329 | } 330 | elseif ($FromFileInfo) { 331 | . Import-DemoClixml 332 | } 333 | } 334 | 335 | } 336 | 337 | 338 | -------------------------------------------------------------------------------- /Commands/Resume-Demo.ps1: -------------------------------------------------------------------------------- 1 | function Resume-Demo 2 | { 3 | <# 4 | .SYNOPSIS 5 | Resumes a Demo 6 | .DESCRIPTION 7 | Resumes a Demo that was paused or debugged with `!`. 8 | .EXAMPLE 9 | Resume-Demo 10 | .LINK 11 | Show-Demo 12 | .LINK 13 | Get-Demo 14 | #> 15 | param( 16 | # The demo that will be resumed. 17 | [Parameter(ValueFromPipeline)] 18 | [PSTypeName('Demo')] 19 | $DemoToResume 20 | ) 21 | 22 | process { 23 | if (-not $DemoToResume -and $demo) { 24 | $DemoToResume = $demo 25 | } 26 | if (-not $DemoToResume) { 27 | Write-Error "No demo to resume" 28 | return 29 | } 30 | $DemoToResume | Format-Custom 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Commands/Show-Demo.ps1: -------------------------------------------------------------------------------- 1 | function Show-Demo 2 | { 3 | <# 4 | .SYNOPSIS 5 | Shows a Demo 6 | .DESCRIPTION 7 | Shows a PowerShell Demo Script. 8 | .EXAMPLE 9 | Show-Demo 10 | .LINK 11 | Get-Demo 12 | #> 13 | [Alias('Start-Demo')] 14 | [CmdletBinding(DefaultParameterSetName='LoadedDemos')] 15 | param( 16 | # The source of the demo. This can be a string, file, command, module, or path. 17 | [Parameter(ValueFromPipelineByPropertyName)] 18 | [Alias('DemoPath','DemoName','DemoText','DemoScript','FullName', 'DemoFile', 'File', 'Source')] 19 | [PSObject] 20 | $From, 21 | 22 | # The name of the chapter 23 | [string] 24 | $Chapter, 25 | 26 | # The current step (within -Chapter) 27 | [ValidateRange(1,10000)] 28 | [int] 29 | $Step, 30 | 31 | # The typing style. Can be letters, words, or none. 32 | [ValidateSet('Letters','Words','None')] 33 | [string] 34 | $TypeStyle = 'Letters', 35 | 36 | # If this is an integer less than 10000, it will be considered 'words per minute' 37 | # Otherwise, this will be the timespan to wait between words / letters being displayed. 38 | [timespan] 39 | $TypeSpeed = [Timespan]"00:00:00.0028", 40 | 41 | # The amount of time to wait between each step. 42 | # If provided, implies -AutoPlay. 43 | [Alias('PauseBetweenSteps')] 44 | [timespan] 45 | $PauseBetweenStep, 46 | 47 | # The amount of time to wait between each line. 48 | # This can help demos that display a lot of information at once. 49 | [Alias('PauseBetweenLines')] 50 | [Timespan] 51 | $PauseBetweenLine = [timespan]"00:00:00.014", 52 | 53 | # If set, will automatically play demos. 54 | # Use -PauseBetweenStep to specify how long to wait between each step. 55 | [switch] 56 | $AutoPlay, 57 | 58 | # If set, will make the demo noniteractive. 59 | [switch] 60 | $NonInteractive, 61 | 62 | # If set, will show the prompt between each step. 63 | # This can also be enabled or disabled within a demo, with .ShowPrompt or .HidePrompt 64 | [switch] 65 | $ShowPrompt, 66 | 67 | # If set, will attempt to record the demo. 68 | # This presumes that [obs-powershell](https://github.com/StartAutomating/obs-powershell) is installed. 69 | [switch] 70 | $Record, 71 | 72 | # If provided, will set the message displayed at demo start. 73 | [string] 74 | $StartMessage, 75 | 76 | # If provided, will set the message displayed at demo start. 77 | [string] 78 | $EndMessage 79 | ) 80 | 81 | process { 82 | $demoFile = 83 | if ($From) { 84 | Get-Demo -From $From 85 | } else { 86 | Get-Demo 87 | } 88 | 89 | 90 | if (-not $demoFile) { 91 | Write-Error "No demo to show" 92 | } 93 | 94 | $demoFile | Add-Member StartMessage $StartMessage -Force 95 | $demoFile | Add-Member EndMessage $EndMessage -Force 96 | 97 | if ($chapter) { 98 | $demoFile | Add-Member CurrentChapter $Chapter -Force 99 | } 100 | if ($step) { 101 | $demoFile | Add-Member CurrentStep $step -Force 102 | } 103 | 104 | if ($Record) { 105 | $demoFile | Add-Member RecordDemo $true -Force 106 | } 107 | 108 | if ($ShowPrompt) { 109 | $demoFile | Add-Member ShowPrompt $true -Force 110 | } 111 | 112 | $demoFile | Add-Member TypeStyle $TypeStyle -Force 113 | if ($TypeStyle -eq 'Letters' -and -not $TypeSpeed) { 114 | $TypeSpeed = [timespan]::FromMilliseconds(27) 115 | } 116 | elseif ($TypeStyle -eq 'Words' -and -not $TypeSpeed) { 117 | $TypeSpeed = [timespan]::FromMilliseconds(37) 118 | } 119 | $demoFile | Add-Member TypeSpeed $TypeSpeed -Force 120 | 121 | 122 | if ($NonInteractive -or 123 | ($Host.Name -eq 'Default Host') -or 124 | $env:BUILD_ID -or 125 | $env:GITHUB_WORKSPACE 126 | ) { 127 | $demoFile | Add-Member Interactive $false -Force 128 | } else { 129 | $demoFile | Add-Member Interactive $true -Force 130 | } 131 | 132 | if ($AutoPlay -or $PauseBetweenStep.TotalMilliseconds) { 133 | if (-not $PauseBetweenStep.TotalMilliseconds) { 134 | $PauseBetweenStep = [timespan]::FromMilliseconds(500) 135 | } 136 | $demoFile | Add-Member Autoplay $true -Force 137 | $demoFile | Add-Member PauseBetweenStep $PauseBetweenStep -Force 138 | } 139 | 140 | if ($PauseBetweenLine.TotalMilliseconds) { 141 | $demoFile | Add-Member PauseBetweenLine $PauseBetweenLine -Force 142 | } 143 | 144 | if ($NonInteractive) { 145 | $demoFile | Format-Custom | Out-String -Width 1mb 146 | } else { 147 | $demoFile | Format-Custom 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Demos/Demo.demo.ps1: -------------------------------------------------------------------------------- 1 | # 1. Hello World in PowerShell 2 | 3 | # .Silent cls 4 | 5 | # 'Hello world' is a really simple script to write in PowerShell. 6 | # You just put it in quotes. 7 | 8 | "hello world" 9 | 10 | # This is because in PowerShell, unassigned output is returned. 11 | 12 | # 2. PowerShell and Objects 13 | 14 | # Everything in PowerShell is an object. 15 | # So I can tell you how many characters there are in hello world just by getting the .Length 16 | "hello world".Length 17 | 18 | # 3. Basic Math in PowerShell 19 | 20 | <# 21 | Math in PowerShell is also really straightforward. 22 | #> 23 | 1 + 1 24 | 25 | 9 / 5 26 | 27 | 1 + 1 + 2 + 1 28 | 29 | # 4. Basic string formatting with PowerShell 30 | 31 | # You can use .NET string formatting with PowerShell by using the -f operator 32 | '{0:c}' -f 1.99 33 | 34 | # The format string in on the left, and the value you're formatting is on the right. 35 | 36 | # '{0:c}' means 'format as currency' 37 | 38 | # The value we are formatting is 1.99. 39 | 40 | # We can also use the .NET type [string] to do the formatting: 41 | [string]::Format("{0:c}", 1.99) 42 | 43 | # You can do some fun things with PowerShell, like multiply strings to repeat them. 44 | '$' * 10 45 | 46 | # 5. The Object Pipeline (is money) 47 | 48 | # A cool and unique part of PowerShell is the object pipeline 49 | 50 | # You can send every object to a command by 'Piping' the object. 51 | 52 | # You can pipe as many commands together as you would like. 53 | 54 | # So you basically program in PowerShell by connecting the dots. 55 | 56 | # To display information in a color, we use the built in command Write-Host. 57 | 58 | # So let's see how much money we can make by connecting the dots. 59 | 60 | # The joke for a long time has been PowerShell + a pulse is $50/hr. 61 | 62 | '$' * 50 | Write-Host 63 | 64 | '{0:c}' -f 50 65 | 66 | # 40 hours a week 67 | '$' * 50 * 40 | Write-Host 68 | 69 | '{0:c}' -f (50 * 40) 70 | 71 | # 52 weeks a year 72 | '$' * 50 * 40 * 52 | Write-Host 73 | '{0:c}' -f (50 * 40 * 52) 74 | 75 | # Learn PowerShell. 76 | # Write Scripts. 77 | # Make Money. -------------------------------------------------------------------------------- /Demos/Trinity-Of-Discoverability.demo.ps1: -------------------------------------------------------------------------------- 1 | #1. Get-Command 2 | 3 | # Get-Command is one of three commands that make up the Trinity of Discoverability. 4 | 5 | # These three commands will help you find your way around what PowerShell can do. 6 | 7 | # Get-Command helps you find out what commands exist. 8 | 9 | Get-Command -Module ShowDemo 10 | 11 | # Because everything is an object in PowerShell, we can pipe this into other commands. 12 | 13 | # So, if we wanted to just get a random loaded command from Show-Demo, we'd use: 14 | 15 | Get-Command -Module ShowDemo | Get-Random 16 | 17 | # In PowerShell, it's common for commands to share similar names. 18 | 19 | # So we can search for commands by wildcard: 20 | 21 | Get-Command *Demo* 22 | 23 | #2. Get-Help 24 | 25 | # Get-Help helps us figure out how commands work. 26 | 27 | Get-Help Show-Demo 28 | 29 | # By default, you see an overview of available help. 30 | 31 | # We can get help about each parameter by using -Parameter * 32 | 33 | Get-Help Show-Demo -Parameter * 34 | 35 | #3. Get-Member 36 | 37 | # Get-Member helps us figure out what properties and methods are available on an object. 38 | 39 | # For example, we can see what properties are available on the output of Get-Command: 40 | 41 | Get-Command -Module ShowDemo | Get-Member 42 | 43 | # Every object has members. Let's get the members the current process. 44 | 45 | Get-Process -Id $PID | Get-Member 46 | 47 | # We can also get -Static members. This is especially useful for classes. 48 | 49 | [Math] | Get-Member -Static 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/powershell 2 | COPY . ./usr/local/share/powershell/Modules/ShowDemo 3 | RUN pwsh -c "New-Item -Path /root/.config/powershell/Microsoft.PowerShell_profile.ps1 -Value 'Import-Module ShowDemo' -Force" 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 James Brundage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | ShowDemo 3 | 4 | 5 | 6 |
7 | ❤️ 8 | 9 |
10 | 11 | 12 | # Showcase your Scripts 13 | 14 | Want to showcase something you built in PowerShell? 15 | 16 | You can make a .demo.ps1 file to showcase your script line by line, like this: 17 | 18 | ![Demo Of ShowDemo](Assets/demo.gif) 19 | 20 | You can also make a .demo.ps1 file as markdown, like [this](demo.md). 21 | 22 | Give it a try! 23 | 24 | ~~~PowerShell 25 | Install-Module ShowDemo -Scope CurrentUser -Force 26 | Import-Module ShowDemo -Force -PassThru 27 | Show-Demo 28 | ~~~ 29 | 30 | ## Writing Demos 31 | 32 | Demo files just simple scripts, named either demo.ps1 or *.demo.ps1. 33 | 34 | Each comment or statement that starts in the first column is considered a step. 35 | 36 | For an example, check out [demo.ps1](https://github.com/StartAutomating/ShowDemo/blob/main/demo.ps1) 37 | 38 | ## Using the GitHub Action 39 | 40 | To use ShowDemo in a GitHub Action, simply add this line to your workflow: 41 | 42 | ~~~yaml 43 | - uses: StartAutomating/ShowDemo@main 44 | ~~~ 45 | 46 | This will take any demo files and export them as markdown. 47 | 48 | ## ShowDemo Commands 49 | 50 | ShowDemo is a module of few commands. They are: 51 | 52 | |Name|Synopsis| 53 | |-|-| 54 | |Get-Demo | Gets Demos | 55 | |Export-Demo| Exports Demos| 56 | |Import-Demo| Imports Demos| 57 | |Resume-Demo| Resumes Demos| 58 | |Show-Demo | Shows Demos | 59 | 60 | You can Show your demo by running: `Show-Demo -DemoPath .\My.demo.ps1` 61 | 62 | Show-Demo is aliased to Start-Demo, it's inspiration 63 | 64 | ## Inspiration, History, and Goals 65 | 66 | In the early days of PowerShell, Jeffery Snover created a useful little script called Start-Demo. 67 | 68 | Start-Demo was incredibly useful. 69 | 70 | It helped showcase just how cool PowerShell could be, and gave every scripter a simple tool to showcase their scripts. 71 | 72 | Start-Demo was written all the way back in PowerShell v1; before the parser API, before markdown, and well before colorized output in Windows Terminal. 73 | 74 | ShowDemo is designed to update and replace the old Start-Demo and provide a foundation to give it even more modern capabilities. 75 | -------------------------------------------------------------------------------- /ShowDemo.ps.psm1: -------------------------------------------------------------------------------- 1 | [Include('*-*.ps1')]$PSScriptRoot 2 | 3 | $thisModule = $MyInvocation.MyCommand.ScriptBlock.Module 4 | $thisModule.pstypenames.insert(0, $thisModule.Name) 5 | $ExecutionContext.SessionState.PSVariable.Set($thisModule.Name, $thisModule) 6 | 7 | $newDriveSplat = [ordered]@{ 8 | Name = $thisModule.Name 9 | PSProvider = 'FileSystem' 10 | Root = $PSScriptRoot 11 | ErrorAction = 'Ignore' 12 | } 13 | 14 | New-PSDrive @newDriveSplat 15 | 16 | Export-ModuleMember -Variable $thisModule.Name -Function * -Alias * -------------------------------------------------------------------------------- /ShowDemo.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Author = 'James Brundage' 3 | CompanyName = 'Start-Automating' 4 | Copyright = '2022-2024 Start-Automating' 5 | Description = 'A simple tool to showcase your scripts.' 6 | Guid = 'c4516317-f99e-44cf-b138-d8c4d1eadf66' 7 | ModuleVersion = '0.1.7' 8 | RootModule = 'ShowDemo.psm1' 9 | FormatsToProcess = 'ShowDemo.format.ps1xml' 10 | TypesToProcess = 'ShowDemo.types.ps1xml' 11 | PrivateData = @{ 12 | PSData = @{ 13 | Tags = 'PowerShell', 'Demo', 'ShowDemo' 14 | ProjectURI = 'https://github.com/StartAutomating/ShowDemo' 15 | LicenseURI = 'https://github.com/StartAutomating/ShowDemo/blob/main/LICENSE' 16 | ReleaseNotes = @' 17 | ## ShowDemo 0.1.7: 18 | 19 | * ShowDemo in Docker (#103) 20 | * Added Dockerfile (#104) 21 | * Publishing all builds to GitHub Container Registry (#105) 22 | * Added Trinity of Discoverability Demo (#51) 23 | * Exporting $ShowDemo (#106) 24 | * Mounting as ShowDemo: (#107) 25 | 26 | --- 27 | 28 | Full history in [CHANGELOG](https://github.com/StartAutomating/ShowDemo/blob/main/CHANGELOG.md) 29 | 30 | > Like It? [Star It](https://github.com/StartAutomating/ShowDemo) 31 | > Love It? [Support It](https://github.com/sponsors/StartAutomating) 32 | '@ 33 | Recommendation = 'obs-powershell', 'Posh' 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ShowDemo.psm1: -------------------------------------------------------------------------------- 1 | :ToIncludeFiles foreach ($file in (Get-ChildItem -Path "$PSScriptRoot" -Filter "*-*.ps1" -Recurse)) { 2 | if ($file.Extension -ne '.ps1') { continue } # Skip if the extension is not .ps1 3 | foreach ($exclusion in '\.[^\.]+\.ps1$') { 4 | if (-not $exclusion) { continue } 5 | if ($file.Name -match $exclusion) { 6 | continue ToIncludeFiles # Skip excluded files 7 | } 8 | } 9 | . $file.FullName 10 | } 11 | 12 | $thisModule = $MyInvocation.MyCommand.ScriptBlock.Module 13 | $thisModule.pstypenames.insert(0, $thisModule.Name) 14 | $ExecutionContext.SessionState.PSVariable.Set($thisModule.Name, $thisModule) 15 | 16 | $newDriveSplat = [ordered]@{ 17 | Name = $thisModule.Name 18 | PSProvider = 'FileSystem' 19 | Root = $PSScriptRoot 20 | ErrorAction = 'Ignore' 21 | } 22 | 23 | New-PSDrive @newDriveSplat 24 | 25 | Export-ModuleMember -Variable $thisModule.Name -Function * -Alias * 26 | -------------------------------------------------------------------------------- /ShowDemo.tests.ps1: -------------------------------------------------------------------------------- 1 | describe ShowDemo { 2 | it 'Shows Demos' { 3 | $showedADemo = Show-Demo -NonInteractive 4 | $showedADemo | Should -match '\e\[' # and it had an escape sequence for color 5 | } 6 | 7 | it 'Can export a demo as markdown' { 8 | $exportedDemo = Get-Demo -DemoName Demo | Export-Demo -OutputPath .\demo.md 9 | $exportedDemo.Extension | Should -be '.md' 10 | $exportedContent = Get-Content $exportedDemo.FullName -Raw 11 | $exportedContent | Should -BeLike '*###*1.*' 12 | $exportedContent | Should -BeLike '*Learn*PowerShell*' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ShowDemo.types.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | Dump 9 | 26 | 27 | 28 | NextChapter 29 | 48 | 49 | 50 | NextStep 51 | 67 | 68 | 69 | ProcessInput 70 | 140 | 141 | 142 | Reset 143 | 159 | 160 | 161 | SetChapter 162 | 203 | 204 | 205 | SetStatus 206 | 219 | 220 | 221 | ShowStep 222 | 295 | 296 | 297 | Start 298 | 310 | 311 | 312 | StartChapter 313 | 316 | 317 | 318 | Stop 319 | 334 | 335 | 336 | ToMarkdown 337 | 365 | 366 | 367 | TotalSteps 368 | 369 | $stepCount = 0 370 | foreach ($chapter in $this.Chapters) { 371 | $stepCount += $chapter.Steps.Length 372 | } 373 | $stepCount 374 | 375 | 376 | 377 | 378 | 379 | Demo.Chapter 380 | 381 | 382 | Steps 383 | 384 | $text = $this.Text 385 | $step = @() 386 | $ThisChapterSteps = @() 387 | 388 | # We want every step to be able to run independently. 389 | # This would be untrue if the code is unbalanced when a chapter would start 390 | # Thus, while we're primarily looking for comments, we also need to track groups 391 | $groupDepth = 0 392 | for ($tokenNumber =0 ; $tokenNumber -lt $this.Tokens.Length; $tokenNumber++) { 393 | $token = $this.tokens[$tokenNumber] 394 | # If the token is a group start 395 | if ($token.Type -eq 'GroupStart') 396 | { 397 | $groupDepth++ # increment depth. 398 | } 399 | # If the token was a group end 400 | elseif ($token.Type -eq 'GroupEnd') 401 | { 402 | $groupDepth-- # decrement depth. 403 | } 404 | 405 | # and 406 | 407 | elseif ( 408 | (-not $groupDepth) -and # If there was no depth and 409 | $token.StartColumn -le 1 -and # the token was a comment starting in the first column 410 | $token[-1].Type -ne 'Keyword' -and # and it wasn't preceeded by a keyword 411 | $token[0].Type -ne 'Keyword' -and # and it wasn't a keyword 412 | $token[0].Type -ne 'Newline' # and it wasn't a newline 413 | ) { 414 | # Then it's the start of a new step 415 | if ($step) { 416 | $stepEnd = $step[-1].Start + $step[-1].Length 417 | $stepStart = $step[0].Start 418 | # Get the content of the last step 419 | $stepScript = $text.Substring($stepStart, $stepEnd - $stepStart) -replace '^\s{0,}$' 420 | if ($stepScript) { 421 | # and make it into a PSObject 422 | $stepScript = [PSObject]::new($stepScript) 423 | # with the PSTypeName 'Demo.Step' 424 | $stepScript.pstypenames.add('Demo.Step') 425 | # and add the .Chapter property, pointing to $this 426 | $stepScript.psobject.properties.add([psnoteproperty]::new( 427 | 'Chapter',$this 428 | )) 429 | 430 | $ThisChapterSteps += $stepScript 431 | } 432 | # then reset the collection of tokens in the current step. 433 | $step = @() 434 | } 435 | } 436 | 437 | # Add any token we see into the current step. 438 | $step += $token 439 | } 440 | 441 | # If there were any steps remaining 442 | if ($step) { 443 | $stepEnd = $step[-1].Start + $step[-1].Length 444 | $stepStart = $step[0].Start 445 | $stepScript = $text.Substring($stepStart, $stepEnd - $stepStart) -replace '^\s{0,}$' 446 | if ($stepScript) { 447 | # make them into 'Demo.Step' objects 448 | $stepScript = [PSObject]::new($stepScript) 449 | $stepScript.pstypenames.add('Demo.Step') 450 | $stepScript.psobject.properties.add([psnoteproperty]::new( 451 | 'Chapter',$this 452 | )) # and add the chapter. 453 | $ThisChapterSteps += $stepScript 454 | } 455 | } 456 | 457 | # Force steps to be returned as a list. 458 | ,$ThisChapterSteps 459 | 460 | 461 | 462 | 463 | 464 | Demo.Step 465 | 466 | 467 | HidePrompt 468 | 487 | 488 | 489 | Invoke 490 | 509 | 510 | 511 | ShowPrompt 512 | 531 | 532 | 533 | Silent 534 | 549 | 550 | 551 | HiddenStep 552 | 553 | $specialStepNameRegex = '^\s{0,}\<{0,1}\#\s{0,}\.(?<st>[\S]+)' 554 | if ($this -notmatch $specialStepNameRegex) { return $null } 555 | [PSCustomObject]@{ 556 | StepType = $matches.st 557 | Arguments = $this -replace $specialStepNameRegex 558 | } 559 | 560 | 561 | 562 | IsComment 563 | 564 | $stepTokens = [Management.Automation.PSParser]::Tokenize($this, [ref]$null) 565 | foreach ($token in $stepTokens) { 566 | if ($token.Type -notin 'Comment', 'Newline') { 567 | return $false 568 | } 569 | } 570 | return $true 571 | 572 | 573 | 574 | 575 | 576 | -------------------------------------------------------------------------------- /Types/Demo.Chapter/get_Steps.ps1: -------------------------------------------------------------------------------- 1 | $text = $this.Text 2 | $step = @() 3 | $ThisChapterSteps = @() 4 | 5 | # We want every step to be able to run independently. 6 | # This would be untrue if the code is unbalanced when a chapter would start 7 | # Thus, while we're primarily looking for comments, we also need to track groups 8 | $groupDepth = 0 9 | for ($tokenNumber =0 ; $tokenNumber -lt $this.Tokens.Length; $tokenNumber++) { 10 | $token = $this.tokens[$tokenNumber] 11 | # If the token is a group start 12 | if ($token.Type -eq 'GroupStart') 13 | { 14 | $groupDepth++ # increment depth. 15 | } 16 | # If the token was a group end 17 | elseif ($token.Type -eq 'GroupEnd') 18 | { 19 | $groupDepth-- # decrement depth. 20 | } 21 | 22 | # and 23 | 24 | elseif ( 25 | (-not $groupDepth) -and # If there was no depth and 26 | $token.StartColumn -le 1 -and # the token was a comment starting in the first column 27 | $token[-1].Type -ne 'Keyword' -and # and it wasn't preceeded by a keyword 28 | $token[0].Type -ne 'Keyword' -and # and it wasn't a keyword 29 | $token[0].Type -ne 'Newline' # and it wasn't a newline 30 | ) { 31 | # Then it's the start of a new step 32 | if ($step) { 33 | $stepEnd = $step[-1].Start + $step[-1].Length 34 | $stepStart = $step[0].Start 35 | # Get the content of the last step 36 | $stepScript = $text.Substring($stepStart, $stepEnd - $stepStart) -replace '^\s{0,}$' 37 | if ($stepScript) { 38 | # and make it into a PSObject 39 | $stepScript = [PSObject]::new($stepScript) 40 | # with the PSTypeName 'Demo.Step' 41 | $stepScript.pstypenames.add('Demo.Step') 42 | # and add the .Chapter property, pointing to $this 43 | $stepScript.psobject.properties.add([psnoteproperty]::new( 44 | 'Chapter',$this 45 | )) 46 | 47 | $ThisChapterSteps += $stepScript 48 | } 49 | # then reset the collection of tokens in the current step. 50 | $step = @() 51 | } 52 | } 53 | 54 | # Add any token we see into the current step. 55 | $step += $token 56 | } 57 | 58 | # If there were any steps remaining 59 | if ($step) { 60 | $stepEnd = $step[-1].Start + $step[-1].Length 61 | $stepStart = $step[0].Start 62 | $stepScript = $text.Substring($stepStart, $stepEnd - $stepStart) -replace '^\s{0,}$' 63 | if ($stepScript) { 64 | # make them into 'Demo.Step' objects 65 | $stepScript = [PSObject]::new($stepScript) 66 | $stepScript.pstypenames.add('Demo.Step') 67 | $stepScript.psobject.properties.add([psnoteproperty]::new( 68 | 'Chapter',$this 69 | )) # and add the chapter. 70 | $ThisChapterSteps += $stepScript 71 | } 72 | } 73 | 74 | # Force steps to be returned as a list. 75 | ,$ThisChapterSteps -------------------------------------------------------------------------------- /Types/Demo.Step/HidePrompt.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Hides the prompt 4 | .DESCRIPTION 5 | Hides the prompt within a demo. 6 | .EXAMPLE 7 | #.HidePrompt 8 | #> 9 | param( 10 | # Any additional parameters for the step. 11 | # This is ignored when hiding prompts. 12 | $step 13 | ) 14 | 15 | $this.Chapter.Demo | Add-Member NoteProperty ShowPrompt $false 16 | 17 | $null = New-Event -SourceIdentifier Demo.HidePrompt -Sender $this.Chapter.Demo -EventArguments @($step) -------------------------------------------------------------------------------- /Types/Demo.Step/Invoke.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Invokes a demo step 4 | .DESCRIPTION 5 | Invokes a step in a demo file. 6 | #> 7 | $hiddenStep = $this.HiddenStep 8 | 9 | $invokeResults = 10 | if (-not $hiddenStep) { 11 | Invoke-Expression $this 12 | } elseif ($this.$($hiddenStep.StepType).Invoke) { 13 | $this.$($hiddenStep.StepType).Invoke($hiddenStep.Arguments) 14 | } 15 | $invokeResults 16 | $null = New-Event -SourceIdentifier Demo.Step.Invoke -Sender $this -EventArguments $args -MessageData $invokeResults 17 | -------------------------------------------------------------------------------- /Types/Demo.Step/ShowPrompt.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Shows the prompt 4 | .DESCRIPTION 5 | Show the prompt within a demo. 6 | .EXAMPLE 7 | #.ShowPrompt 8 | #> 9 | param( 10 | # Any additional parameters for the step. 11 | # This is ignored when showing prompts. 12 | $step 13 | ) 14 | 15 | $this.Chapter.Demo | Add-Member NoteProperty ShowPrompt $true 16 | 17 | $null = New-Event -SourceIdentifier Demo.ShowPrompt -Sender $this.Chapter.Demo -EventArguments @($step) -------------------------------------------------------------------------------- /Types/Demo.Step/Silent.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Run a silent step 4 | .DESCRIPTION 5 | Run a silent step of a demo. 6 | 7 | Silent steps do not display their results. 8 | #> 9 | param($silentStep) 10 | 11 | Invoke-Expression $silentStep | Out-Null 12 | 13 | $null = New-Event -SourceIdentifier Demo.Step.Silent -Sender $this -EventArguments @($silentStep) -------------------------------------------------------------------------------- /Types/Demo.Step/get_HiddenStep.ps1: -------------------------------------------------------------------------------- 1 | $specialStepNameRegex = '^\s{0,}\<{0,1}\#\s{0,}\.(?[\S]+)' 2 | if ($this -notmatch $specialStepNameRegex) { return $null } 3 | [PSCustomObject]@{ 4 | StepType = $matches.st 5 | Arguments = $this -replace $specialStepNameRegex 6 | } -------------------------------------------------------------------------------- /Types/Demo.Step/get_IsComment.ps1: -------------------------------------------------------------------------------- 1 | $stepTokens = [Management.Automation.PSParser]::Tokenize($this, [ref]$null) 2 | foreach ($token in $stepTokens) { 3 | if ($token.Type -notin 'Comment', 'Newline') { 4 | return $false 5 | } 6 | } 7 | return $true -------------------------------------------------------------------------------- /Types/Demo/Demo.format.ps1: -------------------------------------------------------------------------------- 1 | Write-FormatView -TypeName Demo -Property Name, TotalSteps, Chapters -Wrap -VirtualProperty @{ 2 | Chapters = { 3 | @(foreach ($chap in $_.Chapters) { 4 | $chap.Number + ' ' + $chap.Name 5 | }) -join [Environment]::NewLine 6 | } 7 | } 8 | 9 | Write-FormatView -TypeName DemoViewer -Name DemoViewer -AsControl -Action { 10 | Write-FormatViewExpression -If { 11 | # If the demo has not started yet 12 | -not $_.DemoStarted 13 | } -ScriptBlock { 14 | $demo = $_ 15 | 16 | if ($demo.RecordDemo) { 17 | $startRecordingCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Start-Recording','Function,Alias') 18 | if ($startRecordingCommand) { 19 | $null = Start-Recording 20 | } else { 21 | Write-Warning "Start-Recording was not found. Have you installed/imported obs-powershell?" 22 | } 23 | } 24 | 25 | # Start the demo. 26 | $demo.Start() 27 | 28 | # Then, create a message indicating we've started. 29 | if ($demo.StartMessage) { 30 | $demoStartMessage = 31 | Format-RichText -ForegroundColor Warning -InputObject ( 32 | $demo.StartMessage + 33 | ([Environment]::NewLine * 2) 34 | ) -Italic 35 | 36 | # If the demo is being run interactively, 37 | if ($demo.Interactive) { 38 | # write that message to the host. 39 | $demoStartMessage | Out-Host 40 | "" 41 | } 42 | # Otherwise, as long as we are not outputting markdown 43 | elseif (-not $demo.Markdown) { 44 | # output the demo started message. 45 | ($demoStartMessage -join '') + [Environment]::NewLine 46 | } else { 47 | '' 48 | } 49 | } 50 | 51 | if ($demo.Interactive) { 52 | [Console]::OutputEncoding = $OutputEncoding 53 | } 54 | } 55 | 56 | Write-FormatViewExpression -If { 57 | # If the demo has started, is interactive, and not markdown 58 | $_.DemoStarted -and $_.Interactive -and -not $_.Markdown 59 | } { 60 | # attempt to to change the window title. 61 | $Duration = [DateTime]::Now - $_.DemoStarted 62 | $Host.UI.RawUI.WindowTitle = "{0}[{1}m, {2}s] {2}" -f $_.Name, [int]$Duration.TotalMinutes, [int]$Duration.Seconds 63 | "" 64 | } 65 | 66 | 67 | Write-FormatViewExpression -If { 68 | # If we do not have a current chapter 69 | -not $_.CurrentChapter 70 | } -ScriptBlock { 71 | # pick the first chapter and modify the object. 72 | $firstChapter = $_.Chapters[0] 73 | $_ | Add-Member NoteProperty CurrentChapter $firstChapter -Force 74 | $_ | Add-Member NoteProperty CurrentStep 0 -Force 75 | "" 76 | } 77 | 78 | Write-FormatViewExpression -If { 79 | # If we do not have a current step 80 | -not $_.CurrentStep 81 | } -ScriptBlock { 82 | # Start the chapter 83 | $_.StartChapter() 84 | $demo = $_ 85 | # and get the first step 86 | $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1] 87 | 88 | # while the step is hidden 89 | while ($stepToRun.HiddenStep) { 90 | $stepToRun.Invoke() # run it 91 | $demo.NextStep() # and move onto the next step. 92 | $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1] 93 | } 94 | 95 | # declare a chapter heading 96 | $chapterHeading = 97 | $demo.CurrentChapter.Number + 98 | ' ' + 99 | $demo.CurrentChapter.Name + ([Environment]::NewLine * 2) 100 | 101 | $ChapterHeadingSplat = [Ordered]@{ForegroundColor='Verbose';InputObject=$chapterHeading;Underline=$true} 102 | # and get a rich text version of that heading 103 | $null = New-Event -SourceIdentifier Demo.WriteChapterName -Sender $demo -MessageData ([Ordered]@{} + $ChapterHeadingSplat) 104 | $currentChapterText = 105 | Format-RichText @ChapterHeadingSplat 106 | 107 | # If we are running interactively 108 | if ($_.Interactive) { 109 | # output that message now 110 | $currentChapterText | Out-Host 111 | '' 112 | } elseif ($demo.Markdown) { 113 | # otherwise, if we are generating markdown, 114 | # determine the heading size. 115 | $headingSize = [int][math]::max($demo.HeadingSize, 3) 116 | # use Format-Markdown 117 | (Format-Markdown -HeadingSize $headingSize -InputObject $chapterHeading) 118 | } else { 119 | # if we are not running interactively, output the rich text from our formatter. 120 | ($currentChapterText -join '') + [Environment]::NewLine 121 | } 122 | 123 | } 124 | 125 | Write-FormatViewExpression -If { 126 | # If there is a current step 127 | $_.CurrentStep 128 | } -ScriptBlock { 129 | 130 | $demo = $_ 131 | # set step to run 132 | $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1] 133 | 134 | # while the step is hidden 135 | while ($stepToRun.HiddenStep) { 136 | $stepToRun.Invoke() # run it 137 | $demo.NextStep() # and move onto the next step. 138 | $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1] 139 | } 140 | } 141 | 142 | Write-FormatViewExpression -If { 143 | $_.StepToRun -and 144 | $_.ShowPrompt -and 145 | (-not $_.DemoFinished) 146 | } -ScriptBlock { 147 | $promptOutput = prompt 148 | $null = New-Event -SourceIdentifier Demo.WritePrompt -Sender $demo -MessageData $promptOutput 149 | if ($_.Interactive) { 150 | $promptOutput | Out-Host 151 | } # and we're running interactively 152 | else { 153 | $promptOutput | Out-String 154 | } 155 | } 156 | 157 | Write-FormatViewExpression -If { 158 | # If we still have a current step 159 | $_.CurrentStep 160 | } -ScriptBlock { 161 | $demo = $_ 162 | $stepToRun = $demo.CurrentChapter.Steps[$demo.CurrentStep - 1] 163 | # Update the object with it. 164 | $demo | Add-Member NoteProperty StepToRun $stepToRun -Force 165 | # If we are rendering markdown 166 | if ($demo.Markdown) { 167 | # and the step is a comment 168 | if ($stepToRun.IsComment) { 169 | # replace the comment start and end 170 | ("$stepToRun" -replace '^\<{0,1}\#{1}' -replace '\#\>\s{0,}$').Trim() 171 | } 172 | # If the step was not a comment 173 | else 174 | { 175 | # Make it a PowerShell code block. 176 | [Environment]::NewLine + 177 | (Format-Markdown -InputObject $stepToRun.Trim() -CodeLanguage PowerShell) + 178 | [Environment]::NewLine 179 | 180 | } 181 | 182 | # If we are outputting markdown, we're done with this formatting step. 183 | return 184 | } 185 | 186 | # If we are not outputting markdown, then let's figure out our real sleep 187 | $realSleep = 188 | # If it's greater than a millisecond 189 | if ($demo.TypeSpeed.TotalMilliseconds -ge 1) { 190 | $demo.TypeSpeed # trust their input 191 | } elseif ($demo.TypeSpeed.Ticks) 192 | { 193 | # Otherwise, try to convert from 194 | $letterPerMillisecond = 195 | ($demo.TypeSpeed.Ticks * 6) # words per minute 196 | / (60 * 1000) # to milliseconds. 197 | [TimeSpan]::FromMilliseconds($letterPerMillisecond) 198 | } else { 199 | # otherwise, have no delay 200 | [timespan]0 201 | } 202 | 203 | # Now run over each segment of colorized output for the step 204 | $strOut = @(foreach ($output in $demo.ShowStep($stepToRun)) { 205 | $outputCopy = @{} + $output 206 | if ($output.InputObject) { 207 | # Start off by setting the rich text formatting used for this sequence of tokens 208 | $null = New-Event -SourceIdentifier Demo.WriteStep -Sender $demo -MessageData ([Ordered]@{} + $output) 209 | $outputCopy.NoClear = $true 210 | $outputCopy.InputObject = '' 211 | # If we're running interactively, write that to the console now 212 | if ($demo.Interactive) { 213 | [Console]::Write((Format-RichText @outputCopy) -join '') 214 | } else { 215 | # otherwise, add it to $strOut. 216 | Format-RichText @outputCopy 217 | } 218 | # Next, determine our chunks of output 219 | $chunks = 220 | # If we're going letter-by-letter 221 | if ($demo.TypeStyle -eq 'Letters') { 222 | # it's a character array. 223 | "$($output.InputObject)".ToCharArray() 224 | } 225 | # If we're going word by word, 226 | elseif ($demo.TypeStyle -eq 'Words') { 227 | # it's split next to each space. 228 | "$($output.InputObject)" -split '(?=\s)' 229 | } 230 | # otherwise, just output the block 231 | else{ 232 | "$($output.InputObject)" 233 | } 234 | 235 | # Walk over each chunk of output 236 | foreach ($chunk in $chunks) { 237 | if (-not $chunk) { continue } 238 | # If running interactively, 239 | if ($demo.Interactive) { 240 | # write it to the console 241 | [Console]::Write("$chunk") 242 | } else { 243 | # otherwise, add it to $strOut 244 | $chunk 245 | } 246 | 247 | # If we are running interactively, sleep. 248 | if ($realSleep.Ticks -and $demo.Interactive) { 249 | # (this gives us our typing effect) 250 | $null = Start-Sleep -Milliseconds $realSleep.TotalMilliseconds 251 | } 252 | } 253 | 254 | # Now we need to do one more write to close the formatting 255 | $null = $outputCopy.Remove('NoClear') 256 | $outputCopy.InputObject = ' ' 257 | $output.InputObject = '' 258 | # If we're running interactively 259 | if ($demo.Interactive) { 260 | # that goes to the console now. 261 | [Console]::Write((Format-RichText @output) -join '') 262 | } else { 263 | (Format-RichText @output) -join '' 264 | } 265 | } 266 | }) 267 | 268 | # If we are running interactively 269 | if ($demo.Interactive) { 270 | '' # emit nothing from the formatter (since we've already written to the console) 271 | } else { 272 | # otherwise, emit the string. 273 | $strOut -join '' 274 | } 275 | } 276 | 277 | Write-FormatViewExpression -If { 278 | # If the demo is not done and it is interactive 279 | -not $_.DemoFinished -and $_.Interactive -and -not $_.AutoPlay 280 | } -ScriptBlock { 281 | $demo = $_ 282 | # Read input 283 | $hostInput = Read-Host 284 | # and the process it 285 | foreach ($output in $demo.ProcessInput($hostInput)) { 286 | # any output we want to display as a warning 287 | if ($output -is [string]) { 288 | Format-RichText -ForegroundColor Warning -InputObject $output | Out-Host 289 | } 290 | # unless it was a series of splats for Format-RichText 291 | elseif ($output -is [Collections.IDictionary]) { 292 | Format-RichText @output | Out-Host # ( which we will run and output ) 293 | } 294 | # or a scriptblock. 295 | elseif ($output -is [scriptblock]) { 296 | . $output | Out-Host # (which we will run). 297 | } 298 | } 299 | } 300 | 301 | Write-FormatViewExpression -If { 302 | $_.StepToRun # If we have a StepToRun 303 | } -ScriptBlock { 304 | $demo = $_ 305 | # Run it. 306 | $DemoStepOutput = $null 307 | 308 | if ($PSStyle) { 309 | $PSStyle.OutputRendering = 'ANSI' 310 | } 311 | 312 | if ($demo.Interactive) { 313 | [Console]::WriteLine() 314 | if ($demo.PauseBetweenLine) { 315 | # If we're running interactively, pipe it out. 316 | $DemoStepOutput = @(Invoke-Expression -Command $demo.StepToRun *>&1 | 317 | Out-String) -split '(?>\r\n|\n)' 318 | foreach ($demoOutputLine in $DemoStepOutput) { 319 | Start-Sleep -Milliseconds $demo.PauseBetweenLine.TotalMilliseconds 320 | Write-Host $demoOutputLine 321 | } 322 | } else { 323 | # If we're running interactively, pipe it out. 324 | Invoke-Expression -Command $demo.StepToRun *>&1 | 325 | Out-String -OutVariable DemoStepOutput | 326 | Out-Host 327 | } 328 | } 329 | else{ 330 | # Otherwise, pipe it to Out-String 331 | $stepOutput = 332 | Invoke-Expression -Command $demo.StepToRun *>&1 | 333 | Out-String -Width 1kb -OutVariable DemoStepOutput 334 | 335 | # If we're outputting markdown 336 | if ($demo.Markdown) { 337 | # add a newline above and below 338 | [Environment]::NewLine + $( 339 | @( 340 | # If it looks like a tag 341 | if ($stepOutput -match '^\<') { 342 | $stepOutput # include without indentation 343 | } else { 344 | # Otherwise, indent 4 chars so it is seen as preformatted text. 345 | foreach ($line in @($stepOutput -split '(?>\r\n|\n)')) { 346 | (' ' * 4) + $line 347 | } 348 | } 349 | ) -join [Environment]::NewLine 350 | ) + 351 | [Environment]::NewLine 352 | } else { 353 | [Environment]::NewLine + $stepOutput 354 | } 355 | } 356 | 357 | if ($DemoStepOutput) { 358 | $null = New-Event -SourceIdentifier Demo.WriteOutput -Sender $demo -EventArguments $demo.StepToRun -MessageData $DemoStepOutput 359 | } 360 | } 361 | 362 | Write-FormatViewExpression -If { 363 | # If we had a step to run 364 | $_.StepToRun -and 365 | (-not $_.DemoFinished) -and # and the demo's not done 366 | (-not $_.StepToRun.IsComment) -and # and the step is not a comment 367 | (-not $_.Autoplay) -and # and we're not autoplaying 368 | $_.Interactive # and we're running interactively 369 | } -ScriptBlock { 370 | $demo = $_ 371 | 372 | # Prompt and process again 373 | $hostInput = Read-Host 374 | foreach ($output in $demo.ProcessInput($hostInput)) { 375 | if ($output -is [string]) { 376 | Format-RichText -ForegroundColor Warning -InputObject $output | Out-Host 377 | } 378 | elseif ($output -is [Collections.IDictionary]) { 379 | Format-RichText @output | Out-Host 380 | } 381 | elseif ($output -is [scriptblock]) { 382 | . $output | Out-Host 383 | } 384 | } 385 | } 386 | 387 | Write-FormatViewExpression -If { 388 | $_.Autoplay 389 | } -ScriptBlock { 390 | Start-Sleep -Milliseconds $_.PauseBetweenStep.TotalMilliseconds 391 | } 392 | 393 | 394 | Write-FormatViewExpression -If { 395 | # If we had a current step 396 | $_.CurrentStep 397 | } -ScriptBlock { 398 | # Advanced to the next step 399 | $_.NextStep() 400 | } 401 | 402 | Write-FormatViewExpression -If { 403 | -not $_.DemoFinished # If the demo was not finished 404 | } -ControlName DemoViewer -ScriptBlock { 405 | # recursively call this formatter 406 | $_ 407 | } 408 | 409 | Write-FormatViewExpression -If { 410 | # If the demo is finished 411 | $_.DemoFinished 412 | } -ScriptBlock { 413 | $demo = $_ 414 | # figure out how long it took 415 | $duration = $_.DemoFinished - $_.DemoStarted 416 | # change the status 417 | $demo.SetStatus('Finished') 418 | if ($demo.RecordDemo) { 419 | $stopRecording = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Stop-Recording', 'Alias,Function') 420 | if ($stopRecording) { 421 | $recordingOutputFile = Stop-Recording 422 | $newRecordingName = "$($demo.Name).$($demo.DemoStarted.ToString('s') -replace ':', '-')$($recordingOutputFile.Extension)" 423 | if ($recordingOutputFile) { 424 | try { 425 | Copy-Item $recordingOutputFile.FullName -Destination ($recordingOutputFile.FullName | Split-Path | Join-Path -ChildPath $newRecordingName ) 426 | } catch { 427 | Write-Warning "Could not copy $($recordingOutputFile) to $($demo.Name): $($_ | Out-String)" 428 | } 429 | } 430 | } 431 | } 432 | # and prepare a message. 433 | if ($demo.EndMessage) { 434 | $finishedMessage = 435 | Format-RichText -InputObject ( 436 | $demo.EndMessage -f [int]$duration.TotalMinutes, [int]$duration.Seconds 437 | ) -ForegroundColor Warning -Italic 438 | 439 | # If the demo was interactive 440 | if ($demo.Interactive) { 441 | # writ the message 442 | $finishedMessage | Out-Host 443 | # and if we had a nested prompt, exit it 444 | if ($NestedPromptLevel) { 445 | $host.ExitNestedPrompt() 446 | } 447 | } elseif (-not $demo.Markdown) { 448 | $finishedMessage -join '' 449 | } 450 | } 451 | 452 | 453 | # Last but not least, reset the demo. 454 | $demo.Reset() 455 | } 456 | } 457 | 458 | Write-FormatView -TypeName Demo -Action { 459 | Write-FormatViewExpression -ScriptBlock { 460 | # set a script variable to contain the current demo 461 | $ExecutionContext.SessionState.PSVariable.Set('script:currentDemo',$_) 462 | } 463 | Write-FormatViewExpression -ScriptBlock { 464 | $_ # display the demo using the DemoViewer control 465 | } -ControlName DemoViewer 466 | 467 | Write-FormatViewExpression -ScriptBlock { 468 | # unset a script variable 469 | $ExecutionContext.SessionState.PSVariable.Set('script:currentDemo',$null) 470 | } 471 | } 472 | 473 | -------------------------------------------------------------------------------- /Types/Demo/Dump.ps1: -------------------------------------------------------------------------------- 1 | $demoContent = 2 | @(foreach ($chapter in $this.Chapters) { 3 | "# $($chapter.Number) $($chapter.Name)" 4 | $stepIndex = 0 5 | foreach ($step in $chapter.Steps) { 6 | $stepIndex++ 7 | if ($this.CurrentChapter -eq $chapter -and $this.CurrentStep -eq $stepIndex) { 8 | " <# *** You Are Here! *** #>" 9 | } 10 | $step 11 | 12 | } 13 | }) -join [Environment]::NewLine 14 | 15 | $demoContent -------------------------------------------------------------------------------- /Types/Demo/NextChapter.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Go to the Next Chapter in a Demo 4 | .DESCRIPTION 5 | Advances a demo to the next chapter. 6 | #> 7 | $demo = $this 8 | $chapterIndex = $demo.Chapters.IndexOf($demo.CurrentChapter) 9 | $chapterIndex++ 10 | if (-not $demo.Chapters[$chapterIndex]) { 11 | $demo | Add-Member NoteProperty DemoFinished ([datetime]::Now) -Force 12 | $null = New-Event -SourceIdentifier Demo.Complete -Sender $this 13 | } else { 14 | $null = New-Event -SourceIdentifier Demo.NextChapter -Sender $this -EventArguments $demo.Chapters[$chapterIndex].Name -MessageData $demo.Chapters[$chapterIndex] 15 | $demo | Add-Member NoteProperty CurrentChapter $demo.Chapters[$chapterIndex] -Force 16 | $demo | Add-Member NoteProperty CurrentStep 0 -Force 17 | } -------------------------------------------------------------------------------- /Types/Demo/NextStep.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Go to the next demo step 4 | .DESCRIPTION 5 | Advances a demo to the next step. 6 | #> 7 | $demo = $this 8 | $demo.CurrentStep++ 9 | # No more steps in this chapter 10 | if (-not $demo.CurrentChapter.Steps[$demo.CurrentStep - 1]) { 11 | $demo.NextChapter() 12 | } 13 | 14 | $null = New-Event -SourceIdentifier Demo.NextStep -Sender $this -EventArguments $args -------------------------------------------------------------------------------- /Types/Demo/ProcessInput.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Processes demo input 4 | .DESCRIPTION 5 | Processes user input to a demo file. 6 | #> 7 | param($hostInput) 8 | $demo = $this 9 | 10 | $null = New-Event -SourceIdentifier Demo.ProcessInput -Sender $this -EventArguments @($hostinput) 11 | 12 | switch ($hostInput) { 13 | '?' { 14 | @{ 15 | ForegroundColor = 'Cyan' 16 | InputObject = @" 17 | Running demo: $($demo.Name) 18 | (q) Quit 19 | (# ...) Goto Chapter/Step 20 | (f ...) Find cmds using X 21 | (t) Timecheck 22 | (s) Skip 23 | (!) Debug Demo 24 | (d) Dump demo 25 | "@ 26 | } 27 | } 28 | 'q' { 29 | $demo.Stop() 30 | } 31 | 'd' { 32 | $demoDump = $demo.Dump() 33 | $demoDump | Out-Host 34 | if (Get-Command Set-Clipboard -ErrorAction SilentlyContinue) { 35 | $demoDump | Set-Clipboard 36 | } 37 | } 38 | 's' { 39 | $demo | Add-Member NoteProperty StepToRun $null -Force 40 | } 41 | 't' { 42 | $duration = [Datetime]::now - $demo.DemoStarted 43 | @{ 44 | ForegroundColor = 'Warning' 45 | Italic = $true 46 | InputObject = 47 | "{0} {1} [{2}m, {3}s]" -f $demo.Name, $demo.Status, [int]$Duration.TotalMinutes, [int]$Duration.Seconds 48 | } 49 | } 50 | '!' { 51 | Write-Warning "Debugging Demo: Use Resume-Demo to resume." 52 | $host.EnterNestedPrompt() 53 | 54 | } 55 | default { 56 | 57 | if ($hostInput -match '^\s{0,}\#\s{0,}\d') { 58 | $demo | Add-Member NoteProperty StepToRun $null -Force 59 | $demo.SetChapter($hostInput -replace '^\s{0,}\#\s{0,}') 60 | } 61 | elseif ($hostInput -match '^\s{0,}(?>f|/)\s{0,}\S') { 62 | $toFind = $hostInput -replace '^\s{0,}(?>f|/)\s{0,}' 63 | Select-String -Path $demo.DemoFile -Pattern $toFind | Out-Host 64 | {} 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Types/Demo/Reset.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Resets a demo 4 | .DESCRIPTION 5 | Resets a demo file, so that it can be replayed. 6 | #> 7 | $this | Add-Member NoteProperty Status "NotStarted" -Force 8 | $this | Add-Member NoteProperty StepToRun $null -Force 9 | $demo | Add-Member NoteProperty DemoFinished $null -Force 10 | $demo | Add-Member NoteProperty DemoStarted $null -Force 11 | $demo | Add-Member NoteProperty CurrentChapter $null -Force 12 | $demo | Add-Member NoteProperty CurrentStep $null -Force 13 | 14 | $null = New-Event -SourceIdentifier Demo.Reset -Sender $this -EventArguments $args -------------------------------------------------------------------------------- /Types/Demo/SetChapter.ps1: -------------------------------------------------------------------------------- 1 | param([string]$Chapter) 2 | 3 | if (-not $this.Chapters) { throw "No Chapters" } 4 | $chapterParts = @($chapter -split '\.') 5 | $potentialChapterNumbers = @( 6 | if ($chapterParts.Length -gt 1) { 7 | ($chapterParts[0..($chapterParts.Length - 2)] -join '\.') + '*' 8 | } 9 | $chapter + '*' 10 | ) 11 | 12 | 13 | 14 | $newChapter, $newStep = 15 | :nextChapter foreach ($chap in $this.Chapters) { 16 | foreach ($potential in $potentialChapterNumbers) { 17 | if ($chap.Number -like $potential) { 18 | if ($potential -ne ($Chapter + '*')) { 19 | # Setting chapter and step number 20 | $chap, $chapterParts[-1] -as [int] 21 | break nextChapter 22 | } else { 23 | $chap, 1 24 | 25 | } 26 | } 27 | } 28 | 29 | } 30 | 31 | if (-not $newChapter) { 32 | throw "Could not find chapter '$chapter'" 33 | } 34 | 35 | $this | Add-Member NoteProperty CurrentChapter $newChapter -Force 36 | $this | Add-Member NoteProperty CurrentStep ($newStep - 1) -Force 37 | 38 | 39 | -------------------------------------------------------------------------------- /Types/Demo/SetStatus.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets demo status 4 | .DESCRIPTION 5 | Sets the status of a demo. 6 | #> 7 | param([string]$Status) 8 | 9 | $this | Add-Member Status $status -Force 10 | 11 | $null = New-Event -SourceIdentifier Demo.SetStatus -Sender $this -EventArguments @($Status) -------------------------------------------------------------------------------- /Types/Demo/ShowStep.ps1: -------------------------------------------------------------------------------- 1 | param($step) 2 | 3 | $null = New-Event -SourceIdentifier Demo.ShowStep -Sender $this -EventArguments @($step) 4 | 5 | $stepTokens = [Management.Automation.PSParser]::Tokenize($step, [ref]$null) 6 | $PreviousToken = $null 7 | foreach ($_ in $stepTokens) { 8 | $content = $_.Content 9 | if ($_.Type -in 'Variable', 'String') { 10 | $content = $step.Substring($_.Start, $_.Length) 11 | } 12 | if ($PreviousToken) { 13 | $token = $_ 14 | $prevEnd = $PreviousToken.Start + $PreviousToken.Length 15 | $substring = $step.Substring($prevEnd, $token.Start - $prevEnd) 16 | if ($substring) { 17 | @{InputObject=$substring} 18 | } 19 | } 20 | 21 | if ($_.Type -eq 'Comment') { 22 | @{ 23 | ForegroundColor='Success' 24 | InputObject = $content 25 | } 26 | } 27 | elseif ($_.Type -in 'Keyword', 'String', 'CommandArgument') { 28 | @{ 29 | ForegroundColor='Verbose' 30 | InputObject = $Content 31 | } 32 | } 33 | elseif ($_.Type -in 'Variable', 'Command') { 34 | @{ 35 | ForegroundColor='Warning' 36 | InputObject = $Content 37 | } 38 | } 39 | elseif ($_.Type -in 'CommandParameter') { 40 | @{ 41 | ForegroundColor='Magenta' 42 | InputObject = $Content 43 | } 44 | } 45 | elseif ( 46 | $_.Type -in 'Operator','GroupStart', 'GroupEnd' 47 | ) { 48 | @{ 49 | ForegroundColor='Magenta' 50 | InputObject = $Content 51 | } 52 | } 53 | elseif ( 54 | $_.Type -notin 'Comment', 55 | 'Keyword', 'String', 'CommandArgument', 56 | 'Variable', 'Command', 57 | 'CommandParameter', 58 | 'Operator','GroupStart', 'GroupEnd' 59 | ) { 60 | @{ 61 | ForegroundColor='White' 62 | InputObject=$Content 63 | } 64 | } else { 65 | @{ 66 | ForegroundColor='White' 67 | InputObject=$Content 68 | } 69 | } 70 | $PreviousToken = $_ 71 | } -------------------------------------------------------------------------------- /Types/Demo/Start.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Starts a Demo 4 | .DESCRIPTION 5 | Starts a Demo file. 6 | #> 7 | $this | Add-Member NoteProperty Status Running -Force 8 | $this | Add-Member NoteProperty DemoStarted ([DateTime]::Now) -Force 9 | 10 | $null = New-Event -SourceIdentifier Demo.Start -Sender $this -EventArguments $args -------------------------------------------------------------------------------- /Types/Demo/StartChapter.ps1: -------------------------------------------------------------------------------- 1 | $this | Add-Member NoteProperty CurrentStep 1 -Force -------------------------------------------------------------------------------- /Types/Demo/Stop.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Stops a demo 4 | .DESCRIPTION 5 | Stops a demo that is currently running 6 | #> 7 | $this | Add-Member NoteProperty Status Stopped -Force 8 | $this | Add-Member NoteProperty StepToRun $null -Force 9 | $this | Add-Member NoteProperty DemoFinished ([datetime]::Now) -Force 10 | $demo | Add-Member NoteProperty CurrentChapter $null -Force 11 | $demo | Add-Member NoteProperty CurrentStep $null -Force 12 | 13 | $null = New-Event -SourceIdentifier Demo.Stop -Sender $this -EventArguments $args -------------------------------------------------------------------------------- /Types/Demo/ToMarkdown.ps1: -------------------------------------------------------------------------------- 1 | # We don't want to modify this object 2 | $demoCopy = # so create a copy 3 | if ($this.DemoFile) { # by importing the file 4 | Import-Demo -DemoPath $this.DemoFile 5 | } elseif ($this.DemoScript) { # or the script block. 6 | Import-Demo -DemoPath $this.DemoScript 7 | } 8 | 9 | # We need Write-Host to be overridden in the same way as Export-Demo does. 10 | # So find Export-Demo's Abstract Syntax Tree 11 | $exportDemoAst = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Export-Demo','Function').ScriptBlock.Ast.Body 12 | # and find our inner function 13 | $writeHost = $exportDemoAst.Find({param($ast) 14 | $ast.Name -eq 'Write-Host' -and $ast.IsFilter -eq $false 15 | }, $false) 16 | # And override it here 17 | ${function:Write-Host} = $writeHost.Body.GetScriptBlock() 18 | 19 | # Now, modify our demo copy to make it non-interactive 20 | $demoCopy | Add-Member NoteProperty Interactive $false -Force 21 | # and markdown 22 | $demoCopy | Add-Member NoteProperty Markdown $true -Force 23 | # then use the formatter to get the markdown as a string. 24 | $demoCopy | 25 | Format-Custom | 26 | Out-String -Width 1mb -------------------------------------------------------------------------------- /Types/Demo/get_TotalSteps.ps1: -------------------------------------------------------------------------------- 1 | $stepCount = 0 2 | foreach ($chapter in $this.Chapters) { 3 | $stepCount += $chapter.Steps.Length 4 | } 5 | $stepCount -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | 2 | name: DemoPowerShell 3 | description: Make Demos of your PowerShell projects. 4 | inputs: 5 | ShowDemoScript: 6 | required: false 7 | description: | 8 | A PowerShell Script that uses ShowDemo. 9 | Any files outputted from the script will be added to the repository. 10 | If those files have a .Message attached to them, they will be committed with that message. 11 | SkipShowDemoPS1: 12 | required: false 13 | description: 'If set, will not process any files named *.ShowDemo.ps1' 14 | ModuleName: 15 | required: false 16 | description: | 17 | The name of the module for which types and formats are being generated. 18 | If not provided, this will be assumed to be the name of the root directory. 19 | CommitMessage: 20 | required: false 21 | description: If provided, will commit any remaining changes made to the workspace with this commit message. 22 | UserEmail: 23 | required: false 24 | description: The user email associated with a git commit. 25 | UserName: 26 | required: false 27 | description: The user name associated with a git commit. 28 | branding: 29 | icon: terminal 30 | color: blue 31 | runs: 32 | using: composite 33 | steps: 34 | - name: DemoPowerShell 35 | id: DemoPowerShell 36 | shell: pwsh 37 | env: 38 | UserEmail: ${{inputs.UserEmail}} 39 | SkipShowDemoPS1: ${{inputs.SkipShowDemoPS1}} 40 | ModuleName: ${{inputs.ModuleName}} 41 | UserName: ${{inputs.UserName}} 42 | ShowDemoScript: ${{inputs.ShowDemoScript}} 43 | CommitMessage: ${{inputs.CommitMessage}} 44 | run: | 45 | $Parameters = @{} 46 | $Parameters.ShowDemoScript = ${env:ShowDemoScript} 47 | $Parameters.SkipShowDemoPS1 = ${env:SkipShowDemoPS1} 48 | $Parameters.SkipShowDemoPS1 = $parameters.SkipShowDemoPS1 -match 'true'; 49 | $Parameters.ModuleName = ${env:ModuleName} 50 | $Parameters.CommitMessage = ${env:CommitMessage} 51 | $Parameters.UserEmail = ${env:UserEmail} 52 | $Parameters.UserName = ${env:UserName} 53 | foreach ($k in @($parameters.Keys)) { 54 | if ([String]::IsNullOrEmpty($parameters[$k])) { 55 | $parameters.Remove($k) 56 | } 57 | } 58 | Write-Host "::debug:: DemoPowerShell $(@(foreach ($p in $Parameters.GetEnumerator()) {'-' + $p.Key + ' ' + $p.Value}) -join ' ')" 59 | & {<# 60 | .Synopsis 61 | GitHub Action for ShowDemo 62 | .Description 63 | GitHub Action for ShowDemo. This will: 64 | 65 | * Import ShowDemo 66 | * Get all demos in the current directory 67 | * Export each demo to a markdown file. 68 | * Run any .ShowDemo.ps1 scripts 69 | * Run the content of the .ShowDemoScript parameter 70 | 71 | Any files changed can be outputted by the script, and those changes can be checked back into the repo. 72 | Make sure to use the "persistCredentials" option with checkout. 73 | #> 74 | 75 | param( 76 | # A PowerShell Script that uses ShowDemo. 77 | # Any files outputted from the script will be added to the repository. 78 | # If those files have a .Message attached to them, they will be committed with that message. 79 | [string] 80 | $ShowDemoScript, 81 | 82 | # If set, will not process any files named *.ShowDemo.ps1 83 | [switch] 84 | $SkipShowDemoPS1, 85 | 86 | # The name of the module for which types and formats are being generated. 87 | # If not provided, this will be assumed to be the name of the root directory. 88 | [string] 89 | $ModuleName, 90 | 91 | # If provided, will commit any remaining changes made to the workspace with this commit message. 92 | [string] 93 | $CommitMessage, 94 | 95 | # The user email associated with a git commit. 96 | [string] 97 | $UserEmail, 98 | 99 | # The user name associated with a git commit. 100 | [string] 101 | $UserName 102 | ) 103 | 104 | #region Initial Logging 105 | 106 | # Output the parameters passed to this script (for debugging) 107 | "::group::Parameters" | Out-Host 108 | [PSCustomObject]$PSBoundParameters | Format-List | Out-Host 109 | "::endgroup::" | Out-Host 110 | 111 | # Get the GitHub Event 112 | $gitHubEvent = 113 | if ($env:GITHUB_EVENT_PATH) { 114 | [IO.File]::ReadAllText($env:GITHUB_EVENT_PATH) | ConvertFrom-Json 115 | } else { $null } 116 | 117 | # Log the GitHub Event 118 | @" 119 | ::group::GitHubEvent 120 | $($gitHubEvent | ConvertTo-Json -Depth 100) 121 | ::endgroup:: 122 | "@ | Out-Host 123 | 124 | # Check that there is a workspace (and throw if there is not) 125 | if (-not $env:GITHUB_WORKSPACE) { throw "No GitHub workspace" } 126 | 127 | #endregion Initial Logging 128 | 129 | # Check to ensure we are on a branch 130 | $branchName = git rev-parse --abrev-ref HEAD 131 | # If we were not, return. 132 | if (-not $branchName) { 133 | "::warning::Not on a branch" | Out-Host 134 | return 135 | } 136 | 137 | #region Configure UserName and Email 138 | if (-not $UserName) { 139 | $UserName = 140 | if ($env:GITHUB_TOKEN) { 141 | Invoke-RestMethod -uri "https://api.github.com/user" -Headers @{ 142 | Authorization = "token $env:GITHUB_TOKEN" 143 | } | 144 | Select-Object -First 1 -ExpandProperty name 145 | } else { 146 | $env:GITHUB_ACTOR 147 | } 148 | } 149 | 150 | if (-not $UserEmail) { 151 | $GitHubUserEmail = 152 | if ($env:GITHUB_TOKEN) { 153 | Invoke-RestMethod -uri "https://api.github.com/user/emails" -Headers @{ 154 | Authorization = "token $env:GITHUB_TOKEN" 155 | } | 156 | Select-Object -First 1 -ExpandProperty email 157 | } else {''} 158 | $UserEmail = 159 | if ($GitHubUserEmail) { 160 | $GitHubUserEmail 161 | } else { 162 | "$UserName@github.com" 163 | } 164 | } 165 | git config --global user.email $UserEmail 166 | git config --global user.name $UserName 167 | #endregion Configure UserName and Email 168 | 169 | 170 | git pull | Out-Host 171 | 172 | 173 | #region Load Action Module 174 | $ActionModuleName = "ShowDemo" 175 | $ActionModuleFileName = "$ActionModuleName.psd1" 176 | 177 | # Try to find a local copy of the action's module. 178 | # This allows the action to use the current branch's code instead of the action's implementation. 179 | $PSD1Found = Get-ChildItem -Recurse -Filter "*.psd1" | 180 | Where-Object Name -eq $ActionModuleFileName | 181 | Select-Object -First 1 182 | 183 | $ActionModulePath, $ActionModule = 184 | # If there was a .PSD1 found 185 | if ($PSD1Found) { 186 | $PSD1Found.FullName # import from there. 187 | Import-Module $PSD1Found.FullName -Force -PassThru 188 | } 189 | # Otherwise, if we have a GITHUB_ACTION_PATH 190 | elseif ($env:GITHUB_ACTION_PATH) 191 | { 192 | $actionModulePath = Join-Path $env:GITHUB_ACTION_PATH $ActionModuleFileName 193 | if (Test-path $actionModulePath) { 194 | $actionModulePath 195 | Import-Module $actionModulePath -Force -PassThru 196 | } else { 197 | throw "$actionModuleName not found" 198 | } 199 | } 200 | elseif (-not (Get-Module $ActionModuleName)) { 201 | throw "$actionModulePath could not be loaded." 202 | } 203 | 204 | "::notice title=ModuleLoaded::$actionModuleName Loaded from Path - $($actionModulePath)" | Out-Host 205 | #endregion Load Action Module 206 | 207 | 208 | #region Install/Import Other Modules 209 | @" 210 | ::group::Installing Modules 211 | $( 212 | "Installing ugit" | Out-Host 213 | Install-Module -Name ugit -Scope CurrentUser -Force 214 | "Importing ugit" | Out-Host 215 | Import-Module ugit -Force -PassThru -Global | Out-Host 216 | ) 217 | ::endgroup:: 218 | "@ | Out-Host 219 | 220 | 221 | #endregion Install Other Modules 222 | 223 | #region Declare Functions and Variables 224 | $anyFilesChanged = $false 225 | filter ProcessScriptOutput { 226 | $out = $_ 227 | $outItem = Get-Item -Path $out -ErrorAction SilentlyContinue 228 | $fullName, $shouldCommit = 229 | if ($out -is [IO.FileInfo]) { 230 | $out.FullName, (git status $out.Fullname -s) 231 | } elseif ($outItem) { 232 | $outItem.FullName, (git status $outItem.Fullname -s) 233 | } 234 | if ($shouldCommit) { 235 | git add $fullName 236 | if ($out.Message) { 237 | git commit -m "$($out.Message)" 238 | } elseif ($out.CommitMessage) { 239 | git commit -m "$($out.CommitMessage)" 240 | } elseif ($out.SourceFile) { 241 | "Source File: $($out.SourceFile)" | Out-Host 242 | $lastCommitMessage = $out.SourceFile | 243 | git log -n 1 | 244 | Select-Object -ExpandProperty CommitMessage 245 | if ($lastCommitMessage) { 246 | git commit -m $lastCommitMessage 247 | } 248 | } elseif ($gitHubEvent.head_commit.message) { 249 | git commit -m "$($gitHubEvent.head_commit.message)" 250 | } 251 | $anyFilesChanged = $true 252 | } 253 | $out 254 | } 255 | 256 | #endregion Declare Functions and Variables 257 | 258 | 259 | #region Actual Action 260 | 261 | $ShowDemoScriptStart = [DateTime]::Now 262 | if ($ShowDemoScript) { 263 | Invoke-Expression -Command $ShowDemoScript | 264 | . processScriptOutput | 265 | Out-Host 266 | } 267 | $ShowDemoScriptTook = [Datetime]::Now - $ShowDemoScriptStart 268 | $ShowDemoPS1Start = [DateTime]::Now 269 | $ShowDemoPS1List = @() 270 | if (-not $SkipShowDemoPS1) { 271 | $ShowDemoFiles = @( 272 | Get-ChildItem -Recurse -Path $env:GITHUB_WORKSPACE | 273 | Where-Object Name -Match '\.ShowDemo\.ps1$') 274 | 275 | if ($ShowDemoFiles) { 276 | $ShowDemoFiles| 277 | ForEach-Object { 278 | $ShowDemoPS1List += $_.FullName.Replace($env:GITHUB_WORKSPACE, '').TrimStart('/') 279 | $ShowDemoPS1Count++ 280 | "::notice title=Running::$($_.Fullname)" | Out-Host 281 | . $_.FullName | 282 | . processScriptOutput | 283 | Out-Host 284 | } 285 | } 286 | } 287 | 288 | "Fetching Changes" | Out-Host 289 | git fetch --unshallow | Out-Host 290 | 291 | #region Export-Demo 292 | "Looking for demos in $env:GITHUB_WORKSPACE" | Out-Host 293 | Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter *.ps1 | 294 | Where-Object Name -Match '(?<=\.|^)(?>demo|walkthru)\.ps1$' | 295 | ForEach-Object { 296 | $demoFile = $_ 297 | $demoFileOut = 298 | $demoFile | 299 | Export-Demo -OutputPath { 300 | $_.FullName -replace '\.ps1$', '.md' 301 | } 302 | 303 | $lastCommitMessage = 304 | git log $demoFile.FullName | 305 | Select-Object -ExpandProperty CommitMessage -First 1 306 | 307 | "LastCommitMessage for $($demoFile.Name): $lastcommitMessage" | Out-Host 308 | $demoFileOut | 309 | Add-Member NoteProperty CommitMessage $lastCommitMessage -Force -PassThru | 310 | . processScriptOutput 311 | } 312 | 313 | 314 | #endregion Export-Demo 315 | 316 | $ShowDemoPS1EndStart = [DateTime]::Now 317 | $ShowDemoPS1Took = [Datetime]::Now - $ShowDemoPS1Start 318 | if ($CommitMessage -or $anyFilesChanged) { 319 | if ($CommitMessage) { 320 | dir $env:GITHUB_WORKSPACE -Recurse | 321 | ForEach-Object { 322 | $gitStatusOutput = git status $_.Fullname -s 323 | if ($gitStatusOutput) { 324 | git add $_.Fullname 325 | } 326 | } 327 | 328 | git commit -m $ExecutionContext.SessionState.InvokeCommand.ExpandString($CommitMessage) 329 | } 330 | 331 | $checkDetached = git symbolic-ref -q HEAD 332 | if (-not $LASTEXITCODE) { 333 | "::notice::Pulling Updates" | Out-Host 334 | git pull 335 | "::notice::Pushing Changes" | Out-Host 336 | git push 337 | "Git Push Output: $($gitPushed | Out-String)" 338 | } else { 339 | "::notice::Not pushing changes (on detached head)" | Out-Host 340 | $LASTEXITCODE = 0 341 | exit 0 342 | } 343 | } 344 | 345 | #endregion Actual Action 346 | 347 | } @Parameters 348 | 349 | -------------------------------------------------------------------------------- /docs/2022-12-04.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2022/12/04/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2022 12 04" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2022-12.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2022/12/ 3 | --- 4 | {% assign currentYearMonth = "2022 12" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2022.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2022/ 3 | --- 4 | {% assign currentYear = "2022" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "[%B](%m) %Y" %} 8 | {% if postYear != currentYear %} 9 | {% continue %} 10 | {% endif %} 11 | {% if hasDisplayedYear != postYear %} 12 | ## [{{postYear}}](.) 13 | {% endif %} 14 | {% assign hasDisplayedYear = postYear %} 15 | {% if hasDisplayedYearMonth != postYearMonth %} 16 | ### {{postYearMonth}} 17 | {% endif %} 18 | {% assign hasDisplayedYearMonth = postYearMonth %} 19 | * [ {{ post.title }} ]( {{ post.url }} ) 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /docs/2023-05-23.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/05/23/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2023 05 23" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2023-05.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/05/ 3 | --- 4 | {% assign currentYearMonth = "2023 05" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2023-06-15.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/06/15/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2023 06 15" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2023-06.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/06/ 3 | --- 4 | {% assign currentYearMonth = "2023 06" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2023-08-02.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/08/02/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2023 08 02" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2023-08-18.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/08/18/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2023 08 18" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2023-08.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/08/ 3 | --- 4 | {% assign currentYearMonth = "2023 08" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2023-10-10.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/10/10/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2023 10 10" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2023-10.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/10/ 3 | --- 4 | {% assign currentYearMonth = "2023 10" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2023.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2023/ 3 | --- 4 | {% assign currentYear = "2023" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "[%B](%m) %Y" %} 8 | {% if postYear != currentYear %} 9 | {% continue %} 10 | {% endif %} 11 | {% if hasDisplayedYear != postYear %} 12 | ## [{{postYear}}](.) 13 | {% endif %} 14 | {% assign hasDisplayedYear = postYear %} 15 | {% if hasDisplayedYearMonth != postYearMonth %} 16 | ### {{postYearMonth}} 17 | {% endif %} 18 | {% assign hasDisplayedYearMonth = postYearMonth %} 19 | * [ {{ post.title }} ]( {{ post.url }} ) 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /docs/2024-03-09.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2024/03/09/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2024 03 09" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2024-03.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2024/03/ 3 | --- 4 | {% assign currentYearMonth = "2024 03" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2024-04-14.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2024/04/14/ 3 | --- 4 | {% for post in site.posts %} 5 | {% assign currentdate = post.date | date: "%Y %m %d" %} 6 | {% assign friendlydate = post.date | date: "[%B](..) [%d](.) [%Y](../..)" %} 7 | {% if currentdate != "2024 04 14" %} 8 | {% continue %} 9 | {% endif %} 10 | {% if currentdate != date %} 11 | ## {{friendlydate}} 12 | {% assign date = currentdate %} 13 | {% endif %} 14 | * [ {{ post.title }} ]( {{ post.url }} ) 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/2024-04.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2024/04/ 3 | --- 4 | {% assign currentYearMonth = "2024 04" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "%B [%Y](..)" %} 8 | {% assign postYM = post.date | date: "%Y %m" %} 9 | {% if postYM != currentYearMonth %} 10 | {% continue %} 11 | {% endif %} 12 | {% if hasDisplayedYearMonth != postYearMonth %} 13 | ## {{postYearMonth}} 14 | {% endif %} 15 | {% assign hasDisplayedYearMonth = postYearMonth %} 16 | * [ {{ post.title }} ]( {{ post.url }} ) 17 | {% endfor %} 18 | -------------------------------------------------------------------------------- /docs/2024.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /2024/ 3 | --- 4 | {% assign currentYear = "2024" %} 5 | {% for post in site.posts %} 6 | {% assign postYear = post.date | date: "%Y" %} 7 | {% assign postYearMonth = post.date | date: "[%B](%m) %Y" %} 8 | {% if postYear != currentYear %} 9 | {% continue %} 10 | {% endif %} 11 | {% if hasDisplayedYear != postYear %} 12 | ## [{{postYear}}](.) 13 | {% endif %} 14 | {% assign hasDisplayedYear = postYear %} 15 | {% if hasDisplayedYearMonth != postYearMonth %} 16 | ### {{postYearMonth}} 17 | {% endif %} 18 | {% assign hasDisplayedYearMonth = postYearMonth %} 19 | * [ {{ post.title }} ]( {{ post.url }} ) 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /docs/Assets/ShowDemo-animated.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Show 13 | 14 | Demo 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/Assets/ShowDemo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Show 12 | Demo 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/Assets/ShowDemo@1080p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/docs/Assets/ShowDemo@1080p.png -------------------------------------------------------------------------------- /docs/Assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/docs/Assets/demo.gif -------------------------------------------------------------------------------- /docs/Assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StartAutomating/ShowDemo/5390fddbccb1835cabd77e6fb5c70ebc40829654/docs/Assets/demo.mp4 -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | > Like It? [Star It](https://github.com/StartAutomating/ShowDemo) 2 | > Love It? [Support It](https://github.com/sponsors/StartAutomating) 3 | 4 | --- 5 | 6 | ## ShowDemo 0.1.7: 7 | 8 | * ShowDemo in Docker (#103) 9 | * Added Dockerfile (#104) 10 | * Publishing all builds to GitHub Container Registry (#105) 11 | * Added Trinity of Discoverability Demo (#51) 12 | * Exporting $ShowDemo (#106) 13 | * Mounting as ShowDemo: (#107) 14 | 15 | --- 16 | 17 | ## ShowDemo 0.1.6: 18 | 19 | * Show-Demo Syncing Console Encoding ( Fixes #101 ) 20 | * Show-Demo -PauseBetweenLine(s) ( Fixes #100 ) 21 | * Adjusting Default Type Speed ( Fixes #97 ) 22 | * Showing unknown steps in White, not Output ( Fixes #99 ) 23 | 24 | --- 25 | 26 | ## ShowDemo 0.1.5: 27 | 28 | * Demos are now more eventful (#66) 29 | * Nearly every part of ShowDemo transmits PowerShell engine events 30 | * These can be used for highly customized display of demos 31 | * Refactoring all *-Demo commands to use a single -From parameter (#86) 32 | * Added Logo (#90) 33 | * Integrated PSA (#91) 34 | 35 | --- 36 | 37 | ## ShowDemo 0.1.4: 38 | 39 | * ShowDemo - Adding Recommendations (Fixes #63) 40 | * Demo Format - Honoring .StartMessage/.EndMessage (Fixes #62) 41 | * Show-Demo - Adding -StartMessage/-EndMessage (Fixes #61) 42 | 43 | --- 44 | 45 | ## ShowDemo 0.1.3: 46 | 47 | * Adding support for prompts in demos 48 | * Demo.Step - Adding .ShowPrompt()/HidePrompt() (#54/#55) 49 | * Demo Formatting - Supporting ShowPrompt (#56) 50 | * Show-Demo - Adding -ShowPrompt (#53) 51 | * Import-Demo - Linking Chapters (#57) 52 | * Partitioning repository (#48, #49, #50) 53 | 54 | --- 55 | 56 | ## ShowDemo 0.1.2: 57 | 58 | * Get-Demo - Skipping $pwd if in $filePaths (Fixes #43) 59 | * Show-Demo - Adding -Record (Fixes #42) 60 | * Import-Demo - Including .DemoScript (Fixes #44) 61 | * Adding Demo.ToMarkdown (Fixes #45) 62 | 63 | --- 64 | 65 | ## ShowDemo 0.1.1 66 | 67 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep (#39) 68 | * Export-Demo - Defaults to English when invariant culture (Fixes #37) 69 | * Improvements in how steps are determined (#35 #36) 70 | * Please Sponsor ShowDemo (#38) 71 | 72 | --- 73 | 74 | ## ShowDemo 0.1 75 | 76 | Initial Release of Show-Demo. 77 | 78 | * List Demos with Get-Demo 79 | * Show Demos with Show-Demo 80 | * Export Demos with Export-Demo. 81 | * ShowDemo GitHub Action 82 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | showdemo.start-automating.com -------------------------------------------------------------------------------- /docs/Demo/NextChapter.md: -------------------------------------------------------------------------------- 1 | Demo.NextChapter() 2 | ------------------ 3 | 4 | ### Synopsis 5 | Go to the Next Chapter in a Demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Advances a demo to the next chapter. 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Demo/NextStep.md: -------------------------------------------------------------------------------- 1 | Demo.NextStep() 2 | --------------- 3 | 4 | ### Synopsis 5 | Go to the next demo step 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Advances a demo to the next step. 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Demo/ProcessInput.md: -------------------------------------------------------------------------------- 1 | Demo.ProcessInput() 2 | ------------------- 3 | 4 | ### Synopsis 5 | Processes demo input 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Processes user input to a demo file. 12 | 13 | --- 14 | 15 | ### Parameters 16 | #### **hostInput** 17 | 18 | |Type |Required|Position|PipelineInput| 19 | |----------|--------|--------|-------------| 20 | |`[Object]`|false |1 |false | 21 | 22 | --- 23 | -------------------------------------------------------------------------------- /docs/Demo/README.md: -------------------------------------------------------------------------------- 1 | ## Demo 2 | 3 | 4 | ### Script Methods 5 | 6 | 7 | * [NextChapter](NextChapter.md) 8 | * [NextStep](NextStep.md) 9 | * [ProcessInput](ProcessInput.md) 10 | * [Reset](Reset.md) 11 | * [SetStatus](SetStatus.md) 12 | * [Start](Start.md) 13 | * [Stop](Stop.md) 14 | -------------------------------------------------------------------------------- /docs/Demo/Reset.md: -------------------------------------------------------------------------------- 1 | Demo.Reset() 2 | ------------ 3 | 4 | ### Synopsis 5 | Resets a demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Resets a demo file, so that it can be replayed. 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Demo/SetStatus.md: -------------------------------------------------------------------------------- 1 | Demo.SetStatus() 2 | ---------------- 3 | 4 | ### Synopsis 5 | Sets demo status 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Sets the status of a demo. 12 | 13 | --- 14 | 15 | ### Parameters 16 | #### **Status** 17 | 18 | |Type |Required|Position|PipelineInput| 19 | |----------|--------|--------|-------------| 20 | |`[String]`|false |1 |false | 21 | 22 | --- 23 | -------------------------------------------------------------------------------- /docs/Demo/Start.md: -------------------------------------------------------------------------------- 1 | Demo.Start() 2 | ------------ 3 | 4 | ### Synopsis 5 | Starts a Demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Starts a Demo file. 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Demo/Step/HidePrompt.md: -------------------------------------------------------------------------------- 1 | Demo.Step.HidePrompt() 2 | ---------------------- 3 | 4 | ### Synopsis 5 | Hides the prompt 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Hides the prompt within a demo. 12 | 13 | --- 14 | 15 | ### Examples 16 | .HidePrompt 17 | 18 | --- 19 | 20 | ### Parameters 21 | #### **step** 22 | Any additional parameters for the step. 23 | This is ignored when hiding prompts. 24 | 25 | |Type |Required|Position|PipelineInput| 26 | |----------|--------|--------|-------------| 27 | |`[Object]`|false |1 |false | 28 | 29 | --- 30 | -------------------------------------------------------------------------------- /docs/Demo/Step/Invoke.md: -------------------------------------------------------------------------------- 1 | Demo.Step.Invoke() 2 | ------------------ 3 | 4 | ### Synopsis 5 | Invokes a demo step 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Invokes a step in a demo file. 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Demo/Step/README.md: -------------------------------------------------------------------------------- 1 | ## Demo.Step 2 | 3 | 4 | ### Script Methods 5 | 6 | 7 | * [HidePrompt](HidePrompt.md) 8 | * [Invoke](Invoke.md) 9 | * [ShowPrompt](ShowPrompt.md) 10 | * [Silent](Silent.md) 11 | -------------------------------------------------------------------------------- /docs/Demo/Step/ShowPrompt.md: -------------------------------------------------------------------------------- 1 | Demo.Step.ShowPrompt() 2 | ---------------------- 3 | 4 | ### Synopsis 5 | Shows the prompt 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Show the prompt within a demo. 12 | 13 | --- 14 | 15 | ### Examples 16 | .ShowPrompt 17 | 18 | --- 19 | 20 | ### Parameters 21 | #### **step** 22 | Any additional parameters for the step. 23 | This is ignored when showing prompts. 24 | 25 | |Type |Required|Position|PipelineInput| 26 | |----------|--------|--------|-------------| 27 | |`[Object]`|false |1 |false | 28 | 29 | --- 30 | -------------------------------------------------------------------------------- /docs/Demo/Step/Silent.md: -------------------------------------------------------------------------------- 1 | Demo.Step.Silent() 2 | ------------------ 3 | 4 | ### Synopsis 5 | Run a silent step 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Run a silent step of a demo. 12 | 13 | Silent steps do not display their results. 14 | 15 | --- 16 | 17 | ### Parameters 18 | #### **silentStep** 19 | 20 | |Type |Required|Position|PipelineInput| 21 | |----------|--------|--------|-------------| 22 | |`[Object]`|false |1 |false | 23 | 24 | --- 25 | -------------------------------------------------------------------------------- /docs/Demo/Stop.md: -------------------------------------------------------------------------------- 1 | Demo.Stop() 2 | ----------- 3 | 4 | ### Synopsis 5 | Stops a demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Stops a demo that is currently running 12 | 13 | --- 14 | -------------------------------------------------------------------------------- /docs/Export-Demo.md: -------------------------------------------------------------------------------- 1 | Export-Demo 2 | ----------- 3 | 4 | ### Synopsis 5 | Exports Demos 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Exports a Demo. 12 | 13 | Demos can be saved to a Markdown (.md) file or a Clixml (.clixml/.clix)file 14 | 15 | --- 16 | 17 | ### Examples 18 | > EXAMPLE 1 19 | 20 | ```PowerShell 21 | Export-Demo -DemoPath .\demo.ps1 -OutputPath .\demo.md 22 | ``` 23 | 24 | --- 25 | 26 | ### Parameters 27 | #### **From** 28 | The source of the demo. This can be a string, file, command, module, or path. 29 | 30 | |Type |Required|Position|PipelineInput |Aliases | 31 | |------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------------| 32 | |`[PSObject]`|true |named |true (ByPropertyName)|DemoPath
DemoName
DemoText
DemoScript
FullName
DemoFile
File
Source| 33 | 34 | #### **OutputPath** 35 | The output path. This is the location the demo will be saved to. 36 | 37 | |Type |Required|Position|PipelineInput |Aliases | 38 | |----------|--------|--------|---------------------|----------| 39 | |`[String]`|true |named |true (ByPropertyName)|OutputFile| 40 | 41 | --- 42 | 43 | ### Syntax 44 | ```PowerShell 45 | Export-Demo -From -OutputPath [] 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/Get-Demo.md: -------------------------------------------------------------------------------- 1 | Get-Demo 2 | -------- 3 | 4 | ### Synopsis 5 | Gets Demos 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Gets PowerShell Demos. 12 | 13 | Demos located in ShowDemo and all modules that tag ShowDemo will be automatically discovered. 14 | 15 | --- 16 | 17 | ### Related Links 18 | * [Import-Demo](Import-Demo.md) 19 | 20 | --- 21 | 22 | ### Examples 23 | > EXAMPLE 1 24 | 25 | ```PowerShell 26 | Get-Demo 27 | ``` 28 | 29 | --- 30 | 31 | ### Parameters 32 | #### **From** 33 | The source of the demo. This can be a string, file, command, module, or path. 34 | 35 | |Type |Required|Position|PipelineInput |Aliases | 36 | |------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------------| 37 | |`[PSObject]`|true |named |true (ByPropertyName)|DemoPath
DemoName
DemoText
DemoScript
FullName
DemoFile
File
Source| 38 | 39 | --- 40 | 41 | ### Syntax 42 | ```PowerShell 43 | Get-Demo [] 44 | ``` 45 | ```PowerShell 46 | Get-Demo -From [] 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/Import-Demo.md: -------------------------------------------------------------------------------- 1 | Import-Demo 2 | ----------- 3 | 4 | ### Synopsis 5 | Imports Demos 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Imports a Demo script. 12 | 13 | --- 14 | 15 | ### Related Links 16 | * [Export-Demo](Export-Demo.md) 17 | 18 | * [Get-Demo](Get-Demo.md) 19 | 20 | * [Start-Demo](Start-Demo.md) 21 | 22 | --- 23 | 24 | ### Examples 25 | > EXAMPLE 1 26 | 27 | ```PowerShell 28 | Import-Demo -DemoName "Demo" 29 | ``` 30 | 31 | --- 32 | 33 | ### Parameters 34 | #### **From** 35 | The source of the demo. This can be a string, file, command, module, or path. 36 | 37 | |Type |Required|Position|PipelineInput |Aliases | 38 | |------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------------| 39 | |`[PSObject]`|true |named |true (ByPropertyName)|DemoPath
DemoName
DemoText
DemoScript
FullName
DemoFile
File
Source| 40 | 41 | --- 42 | 43 | ### Syntax 44 | ```PowerShell 45 | Import-Demo -From [] 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 | ShowDemo 3 | 4 | 5 | 6 |
7 | ❤️ 8 | 9 |
10 | 11 | 12 | # Showcase your Scripts 13 | 14 | Want to showcase something you built in PowerShell? 15 | 16 | You can make a .demo.ps1 file to showcase your script line by line, like this: 17 | 18 | ![Demo Of ShowDemo](Assets/demo.gif) 19 | 20 | You can also make a .demo.ps1 file as markdown, like [this](demo.md). 21 | 22 | Give it a try! 23 | 24 | ~~~PowerShell 25 | Install-Module ShowDemo -Scope CurrentUser -Force 26 | Import-Module ShowDemo -Force -PassThru 27 | Show-Demo 28 | ~~~ 29 | 30 | ## Writing Demos 31 | 32 | Demo files just simple scripts, named either demo.ps1 or *.demo.ps1. 33 | 34 | Each comment or statement that starts in the first column is considered a step. 35 | 36 | For an example, check out [demo.ps1](https://github.com/StartAutomating/ShowDemo/blob/main/demo.ps1) 37 | 38 | ## Using the GitHub Action 39 | 40 | To use ShowDemo in a GitHub Action, simply add this line to your workflow: 41 | 42 | ~~~yaml 43 | - uses: StartAutomating/ShowDemo@main 44 | ~~~ 45 | 46 | This will take any demo files and export them as markdown. 47 | 48 | ## ShowDemo Commands 49 | 50 | ShowDemo is a module of few commands. They are: 51 | 52 | |Name|Synopsis| 53 | |-|-| 54 | |Get-Demo | Gets Demos | 55 | |Export-Demo| Exports Demos| 56 | |Import-Demo| Imports Demos| 57 | |Resume-Demo| Resumes Demos| 58 | |Show-Demo | Shows Demos | 59 | 60 | You can Show your demo by running: `Show-Demo -DemoPath .\My.demo.ps1` 61 | 62 | Show-Demo is aliased to Start-Demo, it's inspiration 63 | 64 | ## Inspiration, History, and Goals 65 | 66 | In the early days of PowerShell, Jeffery Snover created a useful little script called Start-Demo. 67 | 68 | Start-Demo was incredibly useful. 69 | 70 | It helped showcase just how cool PowerShell could be, and gave every scripter a simple tool to showcase their scripts. 71 | 72 | Start-Demo was written all the way back in PowerShell v1; before the parser API, before markdown, and well before colorized output in Windows Terminal. 73 | 74 | ShowDemo is designed to update and replace the old Start-Demo and provide a foundation to give it even more modern capabilities. 75 | -------------------------------------------------------------------------------- /docs/Resume-Demo.md: -------------------------------------------------------------------------------- 1 | Resume-Demo 2 | ----------- 3 | 4 | ### Synopsis 5 | Resumes a Demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Resumes a Demo that was paused or debugged with `!`. 12 | 13 | --- 14 | 15 | ### Related Links 16 | * [Show-Demo](Show-Demo.md) 17 | 18 | * [Get-Demo](Get-Demo.md) 19 | 20 | --- 21 | 22 | ### Examples 23 | > EXAMPLE 1 24 | 25 | ```PowerShell 26 | Resume-Demo 27 | ``` 28 | 29 | --- 30 | 31 | ### Parameters 32 | #### **DemoToResume** 33 | The demo that will be resumed. 34 | 35 | |Type |Required|Position|PipelineInput | 36 | |----------|--------|--------|--------------| 37 | |`[Object]`|false |1 |true (ByValue)| 38 | 39 | --- 40 | 41 | ### Syntax 42 | ```PowerShell 43 | Resume-Demo [[-DemoToResume] ] [] 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/Show-Demo.md: -------------------------------------------------------------------------------- 1 | Show-Demo 2 | --------- 3 | 4 | ### Synopsis 5 | Shows a Demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Shows a PowerShell Demo Script. 12 | 13 | --- 14 | 15 | ### Related Links 16 | * [Get-Demo](Get-Demo.md) 17 | 18 | --- 19 | 20 | ### Examples 21 | > EXAMPLE 1 22 | 23 | ```PowerShell 24 | Show-Demo 25 | ``` 26 | 27 | --- 28 | 29 | ### Parameters 30 | #### **From** 31 | The source of the demo. This can be a string, file, command, module, or path. 32 | 33 | |Type |Required|Position|PipelineInput |Aliases | 34 | |------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------------| 35 | |`[PSObject]`|false |1 |true (ByPropertyName)|DemoPath
DemoName
DemoText
DemoScript
FullName
DemoFile
File
Source| 36 | 37 | #### **Chapter** 38 | The name of the chapter 39 | 40 | |Type |Required|Position|PipelineInput| 41 | |----------|--------|--------|-------------| 42 | |`[String]`|false |2 |false | 43 | 44 | #### **Step** 45 | The current step (within -Chapter) 46 | 47 | |Type |Required|Position|PipelineInput| 48 | |---------|--------|--------|-------------| 49 | |`[Int32]`|false |3 |false | 50 | 51 | #### **TypeStyle** 52 | The typing style. Can be letters, words, or none. 53 | Valid Values: 54 | 55 | * Letters 56 | * Words 57 | * None 58 | 59 | |Type |Required|Position|PipelineInput| 60 | |----------|--------|--------|-------------| 61 | |`[String]`|false |4 |false | 62 | 63 | #### **TypeSpeed** 64 | If this is an integer less than 10000, it will be considered 'words per minute' 65 | Otherwise, this will be the timespan to wait between words / letters being displayed. 66 | 67 | |Type |Required|Position|PipelineInput| 68 | |------------|--------|--------|-------------| 69 | |`[TimeSpan]`|false |5 |false | 70 | 71 | #### **PauseBetweenStep** 72 | The amount of time to wait between each step. 73 | If provided, implies -AutoPlay. 74 | 75 | |Type |Required|Position|PipelineInput|Aliases | 76 | |------------|--------|--------|-------------|-----------------| 77 | |`[TimeSpan]`|false |6 |false |PauseBetweenSteps| 78 | 79 | #### **PauseBetweenLine** 80 | The amount of time to wait between each line. 81 | This can help demos that display a lot of information at once. 82 | 83 | |Type |Required|Position|PipelineInput|Aliases | 84 | |------------|--------|--------|-------------|-----------------| 85 | |`[TimeSpan]`|false |7 |false |PauseBetweenLines| 86 | 87 | #### **AutoPlay** 88 | If set, will automatically play demos. 89 | Use -PauseBetweenStep to specify how long to wait between each step. 90 | 91 | |Type |Required|Position|PipelineInput| 92 | |----------|--------|--------|-------------| 93 | |`[Switch]`|false |named |false | 94 | 95 | #### **NonInteractive** 96 | If set, will make the demo noniteractive. 97 | 98 | |Type |Required|Position|PipelineInput| 99 | |----------|--------|--------|-------------| 100 | |`[Switch]`|false |named |false | 101 | 102 | #### **ShowPrompt** 103 | If set, will show the prompt between each step. 104 | This can also be enabled or disabled within a demo, with .ShowPrompt or .HidePrompt 105 | 106 | |Type |Required|Position|PipelineInput| 107 | |----------|--------|--------|-------------| 108 | |`[Switch]`|false |named |false | 109 | 110 | #### **Record** 111 | If set, will attempt to record the demo. 112 | This presumes that [obs-powershell](https://github.com/StartAutomating/obs-powershell) is installed. 113 | 114 | |Type |Required|Position|PipelineInput| 115 | |----------|--------|--------|-------------| 116 | |`[Switch]`|false |named |false | 117 | 118 | #### **StartMessage** 119 | If provided, will set the message displayed at demo start. 120 | 121 | |Type |Required|Position|PipelineInput| 122 | |----------|--------|--------|-------------| 123 | |`[String]`|false |8 |false | 124 | 125 | #### **EndMessage** 126 | If provided, will set the message displayed at demo start. 127 | 128 | |Type |Required|Position|PipelineInput| 129 | |----------|--------|--------|-------------| 130 | |`[String]`|false |9 |false | 131 | 132 | --- 133 | 134 | ### Syntax 135 | ```PowerShell 136 | Show-Demo [[-From] ] [[-Chapter] ] [[-Step] ] [[-TypeStyle] ] [[-TypeSpeed] ] [[-PauseBetweenStep] ] [[-PauseBetweenLine] ] [-AutoPlay] [-NonInteractive] [-ShowPrompt] [-Record] [[-StartMessage] ] [[-EndMessage] ] [] 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/Start-Demo.md: -------------------------------------------------------------------------------- 1 | Show-Demo 2 | --------- 3 | 4 | ### Synopsis 5 | Shows a Demo 6 | 7 | --- 8 | 9 | ### Description 10 | 11 | Shows a PowerShell Demo Script. 12 | 13 | --- 14 | 15 | ### Related Links 16 | * [Get-Demo](Get-Demo.md) 17 | 18 | --- 19 | 20 | ### Examples 21 | > EXAMPLE 1 22 | 23 | ```PowerShell 24 | Show-Demo 25 | ``` 26 | 27 | --- 28 | 29 | ### Parameters 30 | #### **From** 31 | The source of the demo. This can be a string, file, command, module, or path. 32 | 33 | |Type |Required|Position|PipelineInput |Aliases | 34 | |------------|--------|--------|---------------------|-----------------------------------------------------------------------------------------------| 35 | |`[PSObject]`|false |1 |true (ByPropertyName)|DemoPath
DemoName
DemoText
DemoScript
FullName
DemoFile
File
Source| 36 | 37 | #### **Chapter** 38 | The name of the chapter 39 | 40 | |Type |Required|Position|PipelineInput| 41 | |----------|--------|--------|-------------| 42 | |`[String]`|false |2 |false | 43 | 44 | #### **Step** 45 | The current step (within -Chapter) 46 | 47 | |Type |Required|Position|PipelineInput| 48 | |---------|--------|--------|-------------| 49 | |`[Int32]`|false |3 |false | 50 | 51 | #### **TypeStyle** 52 | The typing style. Can be letters, words, or none. 53 | Valid Values: 54 | 55 | * Letters 56 | * Words 57 | * None 58 | 59 | |Type |Required|Position|PipelineInput| 60 | |----------|--------|--------|-------------| 61 | |`[String]`|false |4 |false | 62 | 63 | #### **TypeSpeed** 64 | If this is an integer less than 10000, it will be considered 'words per minute' 65 | Otherwise, this will be the timespan to wait between words / letters being displayed. 66 | 67 | |Type |Required|Position|PipelineInput| 68 | |------------|--------|--------|-------------| 69 | |`[TimeSpan]`|false |5 |false | 70 | 71 | #### **PauseBetweenStep** 72 | The amount of time to wait between each step. 73 | If provided, implies -AutoPlay. 74 | 75 | |Type |Required|Position|PipelineInput|Aliases | 76 | |------------|--------|--------|-------------|-----------------| 77 | |`[TimeSpan]`|false |6 |false |PauseBetweenSteps| 78 | 79 | #### **PauseBetweenLine** 80 | The amount of time to wait between each line. 81 | This can help demos that display a lot of information at once. 82 | 83 | |Type |Required|Position|PipelineInput|Aliases | 84 | |------------|--------|--------|-------------|-----------------| 85 | |`[TimeSpan]`|false |7 |false |PauseBetweenLines| 86 | 87 | #### **AutoPlay** 88 | If set, will automatically play demos. 89 | Use -PauseBetweenStep to specify how long to wait between each step. 90 | 91 | |Type |Required|Position|PipelineInput| 92 | |----------|--------|--------|-------------| 93 | |`[Switch]`|false |named |false | 94 | 95 | #### **NonInteractive** 96 | If set, will make the demo noniteractive. 97 | 98 | |Type |Required|Position|PipelineInput| 99 | |----------|--------|--------|-------------| 100 | |`[Switch]`|false |named |false | 101 | 102 | #### **ShowPrompt** 103 | If set, will show the prompt between each step. 104 | This can also be enabled or disabled within a demo, with .ShowPrompt or .HidePrompt 105 | 106 | |Type |Required|Position|PipelineInput| 107 | |----------|--------|--------|-------------| 108 | |`[Switch]`|false |named |false | 109 | 110 | #### **Record** 111 | If set, will attempt to record the demo. 112 | This presumes that [obs-powershell](https://github.com/StartAutomating/obs-powershell) is installed. 113 | 114 | |Type |Required|Position|PipelineInput| 115 | |----------|--------|--------|-------------| 116 | |`[Switch]`|false |named |false | 117 | 118 | #### **StartMessage** 119 | If provided, will set the message displayed at demo start. 120 | 121 | |Type |Required|Position|PipelineInput| 122 | |----------|--------|--------|-------------| 123 | |`[String]`|false |8 |false | 124 | 125 | #### **EndMessage** 126 | If provided, will set the message displayed at demo start. 127 | 128 | |Type |Required|Position|PipelineInput| 129 | |----------|--------|--------|-------------| 130 | |`[String]`|false |9 |false | 131 | 132 | --- 133 | 134 | ### Syntax 135 | ```PowerShell 136 | Show-Demo [[-From] ] [[-Chapter] ] [[-Step] ] [[-TypeStyle] ] [[-TypeSpeed] ] [[-PauseBetweenStep] ] [[-PauseBetweenLine] ] [-AutoPlay] [-NonInteractive] [-ShowPrompt] [-Record] [[-StartMessage] ] [[-EndMessage] ] [] 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | permalink: pretty -------------------------------------------------------------------------------- /docs/_posts/2022-12-04-ShowDemo-0.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1 8 | 9 | Initial Release of Show-Demo. 10 | 11 | * List Demos with Get-Demo 12 | * Show Demos with Show-Demo 13 | * Export Demos with Export-Demo. 14 | * ShowDemo GitHub Action 15 | 16 | -------------------------------------------------------------------------------- /docs/_posts/2022-12-04-Showing-PowerShell-Demos.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: Showing PowerShell Demos 4 | author: StartAutomating 5 | sourceURL: https://github.com/StartAutomating/ShowDemo/issues/1 6 | --- 7 | If you're trying to show off your scripts, typos are not your friend. 8 | 9 | It would be really nice if you could just show your scripts step-by-step. 10 | 11 | Long ago, Jeffery Snover wrote a little demo player, Start-Demo, that did exactly that. This is a cool script, but it got lost to the sands of time, and hasn't been updated too much since V1. 12 | 13 | ShowDemo is a small PowerShell module that makes sleek PowerShell demos. 14 | 15 | Any file named: `*.demo.ps1` or `*.walkthru.ps1` will be considered a demo. 16 | 17 | A demo file will follow this format: 18 | 19 | ~~~PowerShell 20 | 21 | # 1. Chapters are comments that start with a number 22 | 23 | # Comments or statements that start a line and are balanced will be considered a step 24 | 25 | "like this is one step" 26 | 27 | "but so is this whole pipeline, 28 | that stretches for multiple lines" -split 29 | ',' 30 | 31 | # Each step will output in full color 32 | 33 | # and wait for you to press enter 34 | 35 | "then a step will run" 36 | 37 | # You press enter one more time after a command has run 38 | 39 | # And so it goes. 40 | 41 | # step by step 42 | 43 | # until a demo is done 44 | ~~~ 45 | -------------------------------------------------------------------------------- /docs/_posts/2023-05-23-ShowDemo-0.1.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.1 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.1 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.1 8 | 9 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep ([#39](https://github.com/StartAutomating/ShowDemo/issues/39)) 10 | * Export-Demo - Defaults to English when invariant culture (Fixes [#37](https://github.com/StartAutomating/ShowDemo/issues/37)) 11 | * Improvements in how steps are determined ([#35](https://github.com/StartAutomating/ShowDemo/issues/35) [#36](https://github.com/StartAutomating/ShowDemo/issues/36)) 12 | * Please Sponsor ShowDemo ([#38](https://github.com/StartAutomating/ShowDemo/issues/38)) 13 | 14 | --- 15 | 16 | ## ShowDemo 0.1 17 | 18 | Initial Release of Show-Demo. 19 | 20 | * List Demos with Get-Demo 21 | * Show Demos with Show-Demo 22 | * Export Demos with Export-Demo. 23 | * ShowDemo GitHub Action 24 | 25 | -------------------------------------------------------------------------------- /docs/_posts/2023-06-15-ShowDemo-0.1.2.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.2 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.2 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.2: 8 | 9 | * Get-Demo - Skipping $pwd if in $filePaths (Fixes [#43](https://github.com/StartAutomating/ShowDemo/issues/43)) 10 | * Show-Demo - Adding -Record (Fixes [#42](https://github.com/StartAutomating/ShowDemo/issues/42)) 11 | * Import-Demo - Including .DemoScript (Fixes [#44](https://github.com/StartAutomating/ShowDemo/issues/44)) 12 | * Adding Demo.ToMarkdown (Fixes [#45](https://github.com/StartAutomating/ShowDemo/issues/45)) 13 | 14 | --- 15 | 16 | ## ShowDemo 0.1.1: 17 | 18 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep ([#39](https://github.com/StartAutomating/ShowDemo/issues/39)) 19 | * Export-Demo - Defaults to English when invariant culture (Fixes [#37](https://github.com/StartAutomating/ShowDemo/issues/37)) 20 | * Improvements in how steps are determined ([#35](https://github.com/StartAutomating/ShowDemo/issues/35) [#36](https://github.com/StartAutomating/ShowDemo/issues/36)) 21 | * Please Sponsor ShowDemo ([#38](https://github.com/StartAutomating/ShowDemo/issues/38)) 22 | 23 | --- 24 | 25 | ## ShowDemo 0.1: 26 | 27 | Initial Release of Show-Demo. 28 | 29 | * List Demos with Get-Demo 30 | * Show Demos with Show-Demo 31 | * Export Demos with Export-Demo. 32 | * ShowDemo GitHub Action 33 | 34 | -------------------------------------------------------------------------------- /docs/_posts/2023-08-02-ShowDemo-0.1.3.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.3 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.3 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.3: 8 | 9 | * Adding support for prompts in demos 10 | * Demo.Step - Adding .ShowPrompt()/HidePrompt() ([#54](https://github.com/StartAutomating/ShowDemo/issues/54)/[#55](https://github.com/StartAutomating/ShowDemo/issues/55)) 11 | * Demo Formatting - Supporting ShowPrompt ([#56](https://github.com/StartAutomating/ShowDemo/issues/56)) 12 | * Show-Demo - Adding -ShowPrompt ([#53](https://github.com/StartAutomating/ShowDemo/issues/53)) 13 | * Import-Demo - Linking Chapters ([#57](https://github.com/StartAutomating/ShowDemo/issues/57)) 14 | * Partitioning repository ([#48](https://github.com/StartAutomating/ShowDemo/issues/48), [#49](https://github.com/StartAutomating/ShowDemo/issues/49), [#50](https://github.com/StartAutomating/ShowDemo/issues/50)) 15 | 16 | --- 17 | 18 | ## ShowDemo 0.1.2: 19 | 20 | * Get-Demo - Skipping $pwd if in $filePaths (Fixes [#43](https://github.com/StartAutomating/ShowDemo/issues/43)) 21 | * Show-Demo - Adding -Record (Fixes [#42](https://github.com/StartAutomating/ShowDemo/issues/42)) 22 | * Import-Demo - Including .DemoScript (Fixes [#44](https://github.com/StartAutomating/ShowDemo/issues/44)) 23 | * Adding Demo.ToMarkdown (Fixes [#45](https://github.com/StartAutomating/ShowDemo/issues/45)) 24 | 25 | --- 26 | 27 | ## ShowDemo 0.1.1: 28 | 29 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep ([#39](https://github.com/StartAutomating/ShowDemo/issues/39)) 30 | * Export-Demo - Defaults to English when invariant culture (Fixes [#37](https://github.com/StartAutomating/ShowDemo/issues/37)) 31 | * Improvements in how steps are determined ([#35](https://github.com/StartAutomating/ShowDemo/issues/35) [#36](https://github.com/StartAutomating/ShowDemo/issues/36)) 32 | * Please Sponsor ShowDemo ([#38](https://github.com/StartAutomating/ShowDemo/issues/38)) 33 | 34 | --- 35 | 36 | ## ShowDemo 0.1: 37 | 38 | Initial Release of Show-Demo. 39 | 40 | * List Demos with Get-Demo 41 | * Show Demos with Show-Demo 42 | * Export Demos with Export-Demo. 43 | * ShowDemo GitHub Action 44 | 45 | -------------------------------------------------------------------------------- /docs/_posts/2023-08-18-ShowDemo-0.1.4.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.4 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.4 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.4: 8 | 9 | * ShowDemo - Adding Recommendations (Fixes [#63](https://github.com/StartAutomating/ShowDemo/issues/63)) 10 | * Demo Format - Honoring .StartMessage/.EndMessage (Fixes [#62](https://github.com/StartAutomating/ShowDemo/issues/62)) 11 | * Show-Demo - Adding -StartMessage/-EndMessage (Fixes [#61](https://github.com/StartAutomating/ShowDemo/issues/61)) 12 | 13 | --- 14 | 15 | ## ShowDemo 0.1.3: 16 | 17 | * Adding support for prompts in demos 18 | * Demo.Step - Adding .ShowPrompt()/HidePrompt() ([#54](https://github.com/StartAutomating/ShowDemo/issues/54)/[#55](https://github.com/StartAutomating/ShowDemo/issues/55)) 19 | * Demo Formatting - Supporting ShowPrompt ([#56](https://github.com/StartAutomating/ShowDemo/issues/56)) 20 | * Show-Demo - Adding -ShowPrompt ([#53](https://github.com/StartAutomating/ShowDemo/issues/53)) 21 | * Import-Demo - Linking Chapters ([#57](https://github.com/StartAutomating/ShowDemo/issues/57)) 22 | * Partitioning repository ([#48](https://github.com/StartAutomating/ShowDemo/issues/48), [#49](https://github.com/StartAutomating/ShowDemo/issues/49), [#50](https://github.com/StartAutomating/ShowDemo/issues/50)) 23 | 24 | --- 25 | 26 | ## ShowDemo 0.1.2: 27 | 28 | * Get-Demo - Skipping $pwd if in $filePaths (Fixes [#43](https://github.com/StartAutomating/ShowDemo/issues/43)) 29 | * Show-Demo - Adding -Record (Fixes [#42](https://github.com/StartAutomating/ShowDemo/issues/42)) 30 | * Import-Demo - Including .DemoScript (Fixes [#44](https://github.com/StartAutomating/ShowDemo/issues/44)) 31 | * Adding Demo.ToMarkdown (Fixes [#45](https://github.com/StartAutomating/ShowDemo/issues/45)) 32 | 33 | --- 34 | 35 | ## ShowDemo 0.1.1: 36 | 37 | * Show-Demo now supports -AutoPlay/-PauseBetweenStep ([#39](https://github.com/StartAutomating/ShowDemo/issues/39)) 38 | * Export-Demo - Defaults to English when invariant culture (Fixes [#37](https://github.com/StartAutomating/ShowDemo/issues/37)) 39 | * Improvements in how steps are determined ([#35](https://github.com/StartAutomating/ShowDemo/issues/35) [#36](https://github.com/StartAutomating/ShowDemo/issues/36)) 40 | * Please Sponsor ShowDemo ([#38](https://github.com/StartAutomating/ShowDemo/issues/38)) 41 | 42 | --- 43 | 44 | ## ShowDemo 0.1: 45 | 46 | Initial Release of Show-Demo. 47 | 48 | * List Demos with Get-Demo 49 | * Show Demos with Show-Demo 50 | * Export Demos with Export-Demo. 51 | * ShowDemo GitHub Action 52 | 53 | -------------------------------------------------------------------------------- /docs/_posts/2023-10-10-ShowDemo-0.1.5.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.5 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.5 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.5: 8 | 9 | * Demos are now more eventful ([#66](https://github.com/StartAutomating/ShowDemo/issues/66)) 10 | * Nearly every part of ShowDemo transmits PowerShell engine events 11 | * These can be used for highly customized display of demos 12 | * Refactoring all *-Demo commands to use a single -From parameter ([#86](https://github.com/StartAutomating/ShowDemo/issues/86)) 13 | * Added Logo ([#90](https://github.com/StartAutomating/ShowDemo/issues/90)) 14 | * Integrated PSA ([#91](https://github.com/StartAutomating/ShowDemo/issues/91)) 15 | 16 | --- 17 | 18 | Previous release notes in [CHANGELOG](https://github.com/StartAutomating/ShowDemo/blob/main/CHANGELOG.md) 19 | 20 | Like It? Star It! Love It? Support It! 21 | -------------------------------------------------------------------------------- /docs/_posts/2024-03-09-ShowDemo-0.1.6.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.6 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.6 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.6: 8 | 9 | * Show-Demo Syncing Console Encoding ( Fixes [#101](https://github.com/StartAutomating/ShowDemo/issues/101) ) 10 | * Show-Demo -PauseBetweenLine(s) ( Fixes [#100](https://github.com/StartAutomating/ShowDemo/issues/100) ) 11 | * Adjusting Default Type Speed ( Fixes [#97](https://github.com/StartAutomating/ShowDemo/issues/97) ) 12 | * Showing unknown steps in White, not Output ( Fixes [#99](https://github.com/StartAutomating/ShowDemo/issues/99) ) 13 | 14 | --- 15 | 16 | Previous release notes in [CHANGELOG](https://github.com/StartAutomating/ShowDemo/blob/main/CHANGELOG.md) 17 | 18 | Like It? [Star It](https://github.com/StartAutomating/ShowDemo)! Love It? [Support It](https://github.com/sponsors/StartAutomating)! 19 | -------------------------------------------------------------------------------- /docs/_posts/2024-04-14-ShowDemo-0.1.7.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | title: ShowDemo 0.1.7 4 | sourceURL: https://github.com/StartAutomating/ShowDemo/releases/tag/v0.1.7 5 | tag: release 6 | --- 7 | ## ShowDemo 0.1.7: 8 | 9 | * ShowDemo in Docker ([#103](https://github.com/StartAutomating/ShowDemo/issues/103)) 10 | * Added Dockerfile ([#104](https://github.com/StartAutomating/ShowDemo/issues/104)) 11 | * Publishing all builds to GitHub Container Registry ([#105](https://github.com/StartAutomating/ShowDemo/issues/105)) 12 | * Added Trinity of Discoverability Demo ([#51](https://github.com/StartAutomating/ShowDemo/issues/51)) 13 | * Exporting $ShowDemo ([#106](https://github.com/StartAutomating/ShowDemo/issues/106)) 14 | * Mounting as ShowDemo: ([#107](https://github.com/StartAutomating/ShowDemo/issues/107)) 15 | 16 | --- 17 | 18 | Full history in [CHANGELOG](https://github.com/StartAutomating/ShowDemo/blob/main/CHANGELOG.md) 19 | 20 | > Like It? [Star It](https://github.com/StartAutomating/ShowDemo) 21 | > Love It? [Support It](https://github.com/sponsors/StartAutomating) 22 | -------------------------------------------------------------------------------- /docs/rss.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | 5 | 6 | 7 | {{ site.title | xml_escape }} 8 | {{ site.description | xml_escape }} 9 | {{ site.url }}{{ site.baseurl }}/ 10 | 11 | {{ site.time | date_to_rfc822 }} 12 | {{ site.time | date_to_rfc822 }} 13 | Jekyll v{{ jekyll.version }} 14 | {% for post in site.posts limit:1000 %} 15 | {% if post.sitemap != false %} 16 | 17 | {{ post.title | xml_escape }} 18 | {{ post.content | xml_escape }} 19 | {{ post.date | date_to_rfc822 }} 20 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 21 | {{ post.url | prepend: site.baseurl | prepend: site.url }} 22 | {% for tag in post.tags %} 23 | {{ tag | xml_escape }} 24 | {% endfor %} 25 | {% for cat in post.categories %} 26 | {{ cat | xml_escape }} 27 | {% endfor %} 28 | 29 | {% endif %} 30 | {% endfor %} 31 | 32 | 33 | --------------------------------------------------------------------------------