├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── koan-issue.md │ ├── koan-suggestion.md │ ├── module-issue.md │ └── module-suggestion.md ├── pull_request_template.md └── workflows │ └── github.yml ├── .gitignore ├── .vscode ├── PSKoans.code-workspace ├── extensions.json ├── launch.json └── settings.json ├── Build ├── Build-Module.ps1 ├── Initialize-Environment.ps1 ├── Invoke-ModuleTests.ps1 ├── New-Changelog.ps1 └── Register-FileSystemRepository.ps1 ├── CODEOWNERS ├── CONTRIBUTING.md ├── Deploy ├── FileSystem │ └── PSKoans.psdeploy.ps1 ├── PSGallery │ └── PSKoans.psdeploy.ps1 └── Publish.ps1 ├── KoanIndex.md ├── LICENSE ├── PSKoans.ezformat.ps1 ├── PSKoans ├── Classes │ ├── Blank.ps1 │ └── KoanAttribute.ps1 ├── Data │ ├── Advice │ │ ├── Customization │ │ │ └── Profile.Advice.json │ │ ├── Fun │ │ │ ├── FunWithCats.Advice.json │ │ │ └── FunWithGUIs.Advice.json │ │ ├── Interactive │ │ │ ├── CompletionOptions.Advice.json │ │ │ └── EditMode.Advice.json │ │ └── OOP │ │ │ └── OOPIntroduction.Advice.json │ └── Show-MeditationPrompt.Data.psd1 ├── Dependencies.psd1 ├── DummyTypes.ps1 ├── Init │ ├── AddPesterAssertionOperator.ps1 │ ├── ModuleConfiguration.ps1 │ └── RegisterArgumentCompleters.ps1 ├── Koans │ ├── .vscode │ │ └── extensions.json │ ├── Cmdlets 1 │ │ ├── AboutCompareObject.Koans.ps1 │ │ ├── AboutDiscovery.Koans.ps1 │ │ ├── AboutForEachObject.Koans.ps1 │ │ ├── AboutGroupObject.Koans.ps1 │ │ ├── AboutMeasureObject.Koans.ps1 │ │ ├── AboutModules.Koans.ps1 │ │ ├── AboutNewObject.Koans.ps1 │ │ ├── AboutPSProviders.Koans.ps1 │ │ ├── AboutSelectObject.Koans.ps1 │ │ ├── AboutSortObject.Koans.ps1 │ │ ├── AboutTeeObject.Koans.ps1 │ │ └── AboutWhereObject.Koans.ps1 │ ├── Cmdlets 2 │ │ ├── AboutCsvCmdlets.Koans.ps1 │ │ └── AboutOutCmdlets.Koans.ps1 │ ├── Constructs and Patterns │ │ ├── AboutAdvancedFunctions.Koans.ps1 │ │ ├── AboutBitwiseOperations.Koans.ps1 │ │ ├── AboutClasses.Koans.ps1 │ │ ├── AboutCustomObjects.Koans.ps1 │ │ ├── AboutEnumerations.Koans.ps1 │ │ ├── AboutErrorHandling.Koans.ps1 │ │ ├── AboutLists.Koans.ps1 │ │ ├── AboutPSObjects.Koans.ps1 │ │ ├── AboutRedirection.Koans.ps1 │ │ ├── AboutRegularExpressions.Koans.ps1 │ │ ├── AboutSplatting.Koans.ps1 │ │ ├── AboutStringBuilder.Koans.ps1 │ │ └── AboutXml.Koans.ps1 │ ├── Foundations │ │ ├── AboutArrays.Koans.ps1 │ │ ├── AboutAssignmentAndArithmetic.Koans.ps1 │ │ ├── AboutComparison.Koans.ps1 │ │ ├── AboutConditionals.Koans.ps1 │ │ ├── AboutFunctionsAndScriptBlocks.Koans.ps1 │ │ ├── AboutHashtables.Koans.ps1 │ │ ├── AboutLoopsAndPipelines.Koans.ps1 │ │ ├── AboutOrderOfOperations.Koans.ps1 │ │ ├── AboutStringOperators.Koans.ps1 │ │ ├── AboutTypeOperators.Koans.ps1 │ │ └── AboutVariables.Koans.ps1 │ ├── Introduction │ │ ├── AboutAssertions.Koans.ps1 │ │ ├── AboutBinary.Koans.ps1 │ │ ├── AboutBooleans.Koans.ps1 │ │ ├── AboutCmdletVerbs.Koans.ps1 │ │ ├── AboutGetMember.Koans.ps1 │ │ ├── AboutNumbers.Koans.ps1 │ │ └── AboutStrings.Koans.ps1 │ ├── Katas │ │ ├── ProcessingStrings.Koans.ps1 │ │ └── SortingCharacters.Koans.ps1 │ └── Modules │ │ ├── .gitkeep │ │ ├── ActiveDirectory │ │ └── Introduction │ │ │ └── AboutFiltering.Koans.ps1 │ │ └── dbatools │ │ ├── AboutBackupDatabases.Koans.ps1 │ │ ├── AboutDbaDatabase.Koans.ps1 │ │ ├── AboutNewDatabases.Koans.ps1 │ │ ├── AboutQueryingDatabases.Koans.ps1 │ │ └── Mocks │ │ ├── BasicInvokeDbaQuery.xml │ │ ├── Database_All.xml │ │ ├── Database_System.xml │ │ ├── Database_TestDb.xml │ │ ├── MultipleServerQuery.xml │ │ ├── StudentTable.xml │ │ ├── StudentTableBobbySafe.xml │ │ └── StudentTableParams.xml ├── PSKoans.psd1 ├── PSKoans.psm1 ├── Private │ ├── Assert-UnblockedFile.ps1 │ ├── ConvertFrom-WildcardPattern.ps1 │ ├── Get-KoanAst.ps1 │ ├── Get-KoanAttribute.ps1 │ ├── Get-KoanIt.ps1 │ ├── Invoke-Koan.ps1 │ ├── Measure-Koan.ps1 │ ├── Measure-KoanBlockTest.ps1 │ ├── New-KoanRunspace.ps1 │ ├── New-PSKoanErrorRecord.ps1 │ ├── Update-PSKoanFile.ps1 │ └── Write-ConsoleLine.ps1 └── Public │ ├── Get-Blank.ps1 │ ├── Get-Karma.ps1 │ ├── Get-PSKoan.ps1 │ ├── Get-PSKoanLocation.ps1 │ ├── Get-PSKoanSetting.ps1 │ ├── Move-PSKoanLibrary.ps1 │ ├── Register-Advice.ps1 │ ├── Reset-PSKoan.ps1 │ ├── Set-PSKoanLocation.ps1 │ ├── Set-PSKoanSetting.ps1 │ ├── Show-Advice.ps1 │ ├── Show-Karma.ps1 │ └── Update-PSKoan.ps1 ├── README.md ├── Start-DebugSession.ps1 ├── Tests ├── Functions │ ├── Private │ │ ├── Assert-UnblockedFile.Tests.ps1 │ │ ├── ControlTests │ │ │ └── Invoke-Koan.Control_Tests.ps1 │ │ ├── ConvertFrom-WildcardPattern.Tests.ps1 │ │ ├── Get-KoanAst.Tests.ps1 │ │ ├── Get-KoanAttribute.Tests.ps1 │ │ ├── Get-KoanIt.Tests.ps1 │ │ ├── Invoke-Koan.Tests.ps1 │ │ ├── Measure-Koan.Tests.ps1 │ │ ├── New-KoanRunspace.Tests.ps1 │ │ ├── New-PSKoanErrorRecord.Tests.ps1 │ │ └── Update-PSKoanFile.Tests.ps1 │ └── Public │ │ ├── Get-Blank.Tests.ps1 │ │ ├── Get-Karma.Tests.ps1 │ │ ├── Get-PSKoan.Tests.ps1 │ │ ├── Get-PSKoanLocation.Tests.ps1 │ │ ├── Get-PSKoanSetting.Tests.ps1 │ │ ├── Move-PSKoanLibrary.Tests.ps1 │ │ ├── Register-Advice.Tests.ps1 │ │ ├── Reset-PSKoan.Tests.ps1 │ │ ├── Set-PSKoanLocation.Tests.ps1 │ │ ├── Set-PSKoanSetting.Tests.ps1 │ │ ├── Show-Advice.Tests.ps1 │ │ ├── Show-Karma.Tests.ps1 │ │ └── Update-PSKoan.Tests.ps1 ├── KoanValidation.Tests.ps1 ├── ModuleHelp.Tests.ps1 └── ModuleValidation.Tests.ps1 ├── azure-pipelines.yml ├── docs ├── Get-Blank.md ├── Get-Karma.md ├── Get-PSKoan.md ├── Get-PSKoanLocation.md ├── Get-PSKoanSetting.md ├── Move-PSKoanLibrary.md ├── PSKoans.md ├── Register-Advice.md ├── Reset-PSKoan.md ├── Set-PSKoanLocation.md ├── Set-PSKoanSetting.md ├── Show-Advice.md ├── Show-Karma.md └── Update-PSKoan.md ├── drafts ├── AboutOOP.ps1 ├── AboutOOPAnswers │ ├── AboutOOPAnswers.psd1 │ └── AboutOOPAnswers.psm1 ├── ComplexArrayFilter.ps1 └── ExtendedMeasures.ps1 ├── formatting ├── PSKoans.CompleteResult.format.ps1 ├── PSKoans.Controls.format.ps1 ├── PSKoans.KoanInfo.format.ps1 └── PSKoans.Result.format.ps1 ├── images ├── Show-Karma_1.png ├── Show-Karma_2.png ├── logo-128px.png ├── logo-64px.png ├── logo.png └── logo.svg └── templates ├── environment-setup.yml ├── install-built-module.yml ├── register-local-repo.yml └── test-steps.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.md text 7 | *.gitattributes text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.psd1 diff eol=lf 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: vexx32 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: PSKoans # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: joelsallow # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ❓ Ask a Question 4 | url: https://github.com/vexx32/PSKoans/discussions?discussions_q=category%3AQ%26A 5 | about: Ask a general question or look for help with specific koans you're having difficulty with. 6 | - name: 💬 Discuss Koans 7 | url: https://github.com/vexx32/PSKoans/discussions?discussions_q=category%3AGeneral 8 | about: Talk about koans that need some work or new ones you'd like to see, discuss how they could work and what needs to be covered. 9 | - name: 💬 Discuss Module Features 10 | url: https://github.com/vexx32/PSKoans/discussions?discussions_q=category%3AIdeas 11 | about: Discuss features / enhancements you'd like to see in the module. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/koan-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "⚠️ Koan Issue or Bug" 3 | about: Report an issue or bug, or request help with a koan file. 4 | labels: Category-Koans, Issue-Discussion 5 | --- 6 | 7 | # Describe "Koan Bug, Issue, or Help Request" 8 | 9 | 10 | 11 | ## Context "The Problematic Assertions" 12 | 13 | 14 | 15 | ## Context "Your Attempts" 16 | 17 | 18 | 19 | ## Context "Additional Information" 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/koan-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "💡 Koan Suggestion" 3 | about: Suggest a koan, or a list of koans that you feel would add value to the project. 4 | labels: Category-Koans, Issue-Suggestion 5 | --- 6 | 7 | # Describe "Koan(s) You Suggest" 8 | 9 | 10 | 11 | ## Context "The Scope of the Koans You Suggest" 12 | 13 | 14 | 15 | ## Context "Additional Information" 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/module-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "💥 Module Issue or Bug" 3 | about: File an issue or bug report about the module features/functions themselves. 4 | labels: Category-Module, Issue-Discussion 5 | --- 6 | 7 | # Describe "Module Bug or Issue" 8 | 9 | 10 | 11 | ## Context "The Problem" 12 | 13 | 14 | 15 | ## Context "Expected Behavior" 16 | 17 | 18 | 19 | ## Context "Additional Information" 20 | 21 | 22 | 23 | ```powershell 24 | Get-Module -Name PSKoans -ListAvailable | 25 | Select-Object -Property Name, Version 26 | 27 | $PSVersionTable | Out-String 28 | ``` 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/module-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Module Suggestion" 3 | about: Suggest new module functionality, or enhancements to existing functionality. 4 | labels: Category-Module, Issue-Suggestion 5 | 6 | --- 7 | 8 | # Describe "Functionality" 9 | 10 | 11 | 12 | ## Context "Private or Public? What are the use cases? Parameters? Options?" 13 | 14 | 15 | 16 | ## Context "Additional Information" 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # PR Summary 2 | 3 | 12 | 13 | ## Context 14 | 15 | 18 | 19 | ## Changes 20 | 21 | 24 | 25 | ## Checklist 26 | 27 | - [ ] Pull Request has a meaningful title. 28 | - [ ] Summarised changes. 29 | - [ ] Pull Request is ready to merge & is not WIP. 30 | - [ ] Added tests / only testable interactively. 31 | - Make sure you add a new test if old tests do not effectively test the code changed. 32 | - [ ] Added documentation / opened issue to track adding documentation at a later date. 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .DS_Store/ 3 | *.temppoint.* 4 | *.format.ps1xml 5 | PSKoans.psproj 6 | 7 | PSKoans/*/PSKoans-help.xml 8 | -------------------------------------------------------------------------------- /.vscode/PSKoans.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.PowerShell", 4 | "ms-vscode.PowerShell-Preview", 5 | "streetsidesoftware.code-spell-checker", 6 | "yzhang.markdown-all-in-one" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "PowerShell Launch Script", 9 | "type": "PowerShell", 10 | "request": "launch", 11 | "script": "./Start-DebugSession.ps1", 12 | "cwd": "${workspaceFolder}" 13 | }, 14 | { 15 | "name": "PowerShell: Launch Current File", 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "script": "${file}", 19 | "cwd": "${file}" 20 | }, 21 | { 22 | "name": "PowerShell Interactive Session", 23 | "type": "PowerShell", 24 | "request": "launch", 25 | "cwd": "" 26 | }, 27 | { 28 | "name": "PowerShell Attach Interactive Session Runspace", 29 | "type": "PowerShell", 30 | "request": "attach", 31 | "processId": "current" 32 | }, 33 | { 34 | "name": "PowerShell Attach to Host Process", 35 | "type": "PowerShell", 36 | "request": "attach", 37 | "runspaceId": 1 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Cmdlet", 4 | "Cmdlets", 5 | "Dbatools", 6 | "Hashtables", 7 | "Katas" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /Build/Build-Module.ps1: -------------------------------------------------------------------------------- 1 | # Grab nuget bits, set build variables, start build. 2 | Get-PackageProvider -Name NuGet -ForceBootstrap > $null 3 | 4 | # Create format.ps1xml file 5 | & "$PSScriptRoot/../PSKoans.ezformat.ps1" 6 | 7 | Import-Module "$env:PROJECTROOT/PSKoans" 8 | 9 | Set-BuildEnvironment 10 | 11 | $Lines = '-' * 70 12 | 13 | Write-Host $Lines 14 | Write-Host "STATUS: Generating External Help and Building Module" 15 | Write-Host $Lines 16 | 17 | # Load the module, read the exported functions, update the psd1 FunctionsToExport 18 | Set-ModuleFunction 19 | 20 | # Bump the module version if we didn't already 21 | try { 22 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 23 | 24 | $GalleryVersion = Get-NextNugetPackageVersion -Name $env:BHProjectName -ErrorAction Stop 25 | $GithubVersion = Get-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -ErrorAction Stop 26 | 27 | if ($GalleryVersion -ge $GithubVersion) { 28 | Update-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -Value $GalleryVersion -ErrorAction Stop 29 | } 30 | } 31 | catch { 32 | Write-Host "Failed to update version for '$env:BHProjectName': $_." 33 | Write-Host "Continuing with existing version." 34 | } 35 | 36 | # Build external help files from Platyps MD files 37 | New-ExternalHelp -Path "$env:PROJECTROOT/docs/" -OutputPath "$env:PROJECTROOT/PSKoans/en" 38 | 39 | Copy-Item -Path "$env:PROJECTROOT/PSKoans" -Destination $env:BUILTMODULEPATH -Recurse -PassThru | 40 | Where-Object { -not $_.PSIsContainer } 41 | -------------------------------------------------------------------------------- /Build/Initialize-Environment.ps1: -------------------------------------------------------------------------------- 1 | Get-PackageProvider -Name NuGet -ForceBootstrap > $null 2 | 3 | Set-BuildEnvironment 4 | 5 | $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/.." 6 | Write-Host "##vso[task.setvariable variable=ProjectRoot]$ProjectRoot" 7 | 8 | $Lines = '-' * 70 9 | 10 | Write-Host $Lines 11 | Write-Host "Repository Branch: $env:BUILD_SOURCEBRANCHNAME ($env:BUILD_SOURCEBRANCH)" 12 | Write-Host "Build System Details:" 13 | Get-Item 'Env:BH*' | Out-String | Write-Host 14 | Write-Host $Lines 15 | -------------------------------------------------------------------------------- /Build/Invoke-ModuleTests.ps1: -------------------------------------------------------------------------------- 1 | $Lines = '-' * 70 2 | 3 | Import-Module 'PSKoans' 4 | 5 | $PesterVersion = (Get-Module -Name Pester).Version 6 | $PSVersion = $PSVersionTable.PSVersion 7 | 8 | Write-Host $Lines 9 | Write-Host "TEST: PowerShell Version: $PSVersion" 10 | Write-Host "TEST: Pester Version: $PesterVersion" 11 | Write-Host $Lines 12 | 13 | try { 14 | # Try/Finally required since -CI will exit with exit code on failure. 15 | Invoke-Pester -Path "$env:PROJECTROOT" -CI -Output Normal 16 | } 17 | finally { 18 | $Timestamp = Get-Date -Format "yyyyMMdd-hhmmss" 19 | $TestFile = "PS${PSVersion}_${TimeStamp}_PSKoans.TestResults.xml" 20 | $CodeCoverageFile = "PS${PSVersion}_${TimeStamp}_PSKoans.CodeCoverage.xml" 21 | 22 | $ModuleFolders = @( 23 | Get-Item -Path "$env:PROJECTROOT/PSKoans" 24 | Get-ChildItem -Path "$env:PROJECTROOT/PSKoans" -Directory -Recurse | 25 | Where-Object FullName -NotMatch '[\\/]Tests[\\/]|[\\/]PSKoans[\\/]Koans[\\/]' 26 | ).FullName -join ';' 27 | 28 | $AzurePipelines = $env:BUILD_SOURCESDIRECTORY -and $env:BUILD_BUILDNUMBER 29 | $GithubActions = [bool]$env:GITHUB_WORKSPACE 30 | 31 | if ($AzurePipelines) { 32 | # Tell Azure what the test results & code coverage file names will be 33 | Write-Host "##vso[task.setvariable variable=TestResults]$TestFile" 34 | Write-Host "##vso[task.setvariable variable=CodeCoverageFile]$CodeCoverageFile" 35 | Write-Host "##vso[task.setvariable variable=SourceFolders]$ModuleFolders" 36 | 37 | # Move files generated from Invoke-Pester to expected location 38 | Move-Item -Path './testResults.xml' -Destination "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$TestFile" 39 | Move-Item -Path './coverage.xml' -Destination "$env:BUILD_ARTIFACTSTAGINGDIRECTORY/$CodeCoverageFile" 40 | } 41 | elseif ($GithubActions) { 42 | @( 43 | "TestResults=$TestFile" 44 | "CodeCoverageFile=$CodeCoverageFile" 45 | "SourceFolders=$ModuleFolders" 46 | ) | Add-Content -Path $env:GITHUB_ENV 47 | 48 | Move-Item -Path './testResults.xml' -Destination "$env:GITHUB_WORKSPACE/$TestFile" 49 | Move-Item -Path './coverage.xml' -Destination "$env:GITHUB_WORKSPACE/$CodeCoverageFile" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Build/New-Changelog.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creates a Markdown changelog file with table formatting. 4 | 5 | .DESCRIPTION 6 | Uses `git log` to compare the current commit to the last tagged commit, 7 | and parses the output into CSV data. This is then processed and email 8 | addresses of commit authors are used to retrieve their Github 9 | usernames, if available. 10 | 11 | The final result is stored in a Markdown plaintext file at the 12 | designated path. 13 | 14 | .PARAMETER Path 15 | The file location to save the markdown text to. The file will be 16 | overwritten if it already contains data. 17 | 18 | .PARAMETER CommitID 19 | The commit tag or hash that is used to identify the commit to 20 | compare with. By default, the last value given by `git tag` 21 | will be used. 22 | 23 | .PARAMETER ApiKey 24 | The Github API key to use when looking up commit authors' 25 | Github user names. 26 | 27 | .EXAMPLE 28 | New-Changelog.ps1 -Path File.md -ApiKey $GHApiKey 29 | 30 | Retrieves the commits since the last tagged commit and creates a 31 | Markdown-formatted plaintext file called File.md in the current 32 | location. 33 | 34 | .NOTES 35 | The Github API limitation of 60 requests per minute is -barely- 36 | usable without authentication. Authenticated requests have a 37 | substantially higher limit on requests per minute. 38 | #> 39 | [CmdletBinding()] 40 | param( 41 | [Parameter(Mandatory, Position = 0)] 42 | [ValidateScript( { Test-Path $_ -IsValid })] 43 | [string] 44 | $Path, 45 | 46 | [Parameter(Position = 1)] 47 | [ValidatePattern('[a-f0-9]{6,40}|v?(\d+\.)+\d+(-\w+\d+)?')] 48 | [string] 49 | $CommitID = (git tag | Select-Object -Last 1), 50 | 51 | [Parameter()] 52 | [Alias('OauthToken')] 53 | [string] 54 | $ApiKey 55 | ) 56 | 57 | begin { 58 | if ($ApiKey) { 59 | $RequestParams = @{ 60 | SessionVariable = 'AuthSession' 61 | Uri = 'https://api.github.com/' 62 | Headers = @{ Authorization = "token $ApiKey" } 63 | } 64 | Invoke-RestMethod @RequestParams | Out-String | Write-Verbose 65 | } 66 | } 67 | process { 68 | $RequestParams = if ($AuthSession) { @{ WebSession = $AuthSession } } else { @{ } } 69 | $Args = @( 70 | '--no-pager' 71 | 'log' 72 | '--first-parent' 73 | "$CommitID..HEAD" 74 | '--format="%H~%aN~%aE~%s"' 75 | '--' 76 | '.' 77 | '":(exclude)*.md"' 78 | ) 79 | $Commits = & git @args | ConvertFrom-Csv -Delimiter '~' -Header Hash, Name, Email, Subject 80 | 81 | $NameTable = @{ } 82 | foreach ($Item in ($Commits | Sort-Object -Property Email -Unique)) { 83 | $RequestParams['Uri'] = "https://api.github.com/search/users?q=$($Item.Email)+in:email" 84 | 85 | do { 86 | $result = $null 87 | $attempt = 0 88 | try { 89 | $result = Invoke-RestMethod @RequestParams -ErrorAction Stop 90 | } 91 | catch { 92 | # If this errors, we have probably hit Github's per-minute API restriction, 93 | # so wait at least 30 seconds before retry. 94 | Start-Sleep -Seconds 30 95 | $attempt++ 96 | } 97 | } while ($attempt -lt 3 -and -not $result) 98 | 99 | $NameTable[$Item.Email] = if ($result.total_count) { $result.items[0].login } else { $Item.Name } 100 | } 101 | 102 | $CsvString = $Commits | 103 | Select-Object -Property @( 104 | @{ Name = 'Hash'; Expression = { $_.Hash.Substring(0, 7) } } 105 | @{ Name = 'Name'; Expression = { $NameTable[$_.Email] } } 106 | 'Subject' 107 | ) | 108 | ConvertTo-Csv -Delimiter '|' 109 | 110 | $TableRows = $CsvString -replace '"', ' ' -replace '^|$', '|' 111 | 112 | $MarkdownTable = @( 113 | # Header Row Only 114 | $TableRows | Select-Object -First 1 115 | # Adding Markdown table column alignments 116 | "| :--: | :--- | :--- |" 117 | # Data Rows 118 | $TableRows | Select-Object -Skip 1 119 | ) 120 | 121 | $MarkdownTable | Set-Content -Path $Path 122 | } 123 | -------------------------------------------------------------------------------- /Build/Register-FileSystemRepository.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [Parameter(Mandatory)] 4 | [string] 5 | $Path, 6 | 7 | [Parameter(Mandatory)] 8 | [string] 9 | $Name 10 | ) 11 | 12 | $RepositoryFolder = if (-not (Test-Path $Path)) { 13 | New-Item -ItemType Directory -Path $Path -Force 14 | } 15 | else { 16 | Get-Item -Path $Path 17 | } 18 | 19 | $Params = @{ 20 | Name = $Name 21 | SourceLocation = $RepositoryFolder.FullName 22 | ScriptSourceLocation = $RepositoryFolder.FullName 23 | InstallationPolicy = 'Trusted' 24 | } 25 | Register-PSRepository @Params 26 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @vexx32 @steviecoaster 2 | -------------------------------------------------------------------------------- /Deploy/FileSystem/PSKoans.psdeploy.ps1: -------------------------------------------------------------------------------- 1 | Deploy Module { 2 | By PSGalleryModule { 3 | FromSource "$PSScriptRoot/../PSKoans" 4 | To FileSystem 5 | WithOptions @{ 6 | ApiKey = 'FileSystem' 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Deploy/PSGallery/PSKoans.psdeploy.ps1: -------------------------------------------------------------------------------- 1 | Deploy Module { 2 | By PSGalleryModule { 3 | FromSource "$PSScriptRoot/../PSKoans" 4 | To PSGallery 5 | WithOptions @{ 6 | ApiKey = $ENV:NugetApiKey 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Deploy/Publish.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [string] 4 | $Key, 5 | 6 | [string] 7 | $Path, 8 | 9 | [string] 10 | $OutputDirectory 11 | ) 12 | 13 | $env:NugetApiKey = $Key 14 | 15 | if ($OutputDirectory) { 16 | Import-Module "$PSScriptRoot/PSKoans" 17 | $Module = Get-Module -Name PSKoans 18 | $Dependencies = @( 19 | $Module.RequiredModules.Name 20 | $Module.NestedModules.Name 21 | ).Where{ $_ } 22 | 23 | foreach ($Module in $Dependencies) { 24 | Publish-Module -Name $Module -Repository FileSystem -NugetApiKey "Test-Publish" 25 | } 26 | } 27 | 28 | $HelpFile = Get-ChildItem -Path "$PSScriptRoot/PSKoans" -File -Recurse -Filter '*-help.xml' 29 | 30 | if ($HelpFile.Directory -notmatch 'en|\w{1,2}(-\w{1,2})?') { 31 | $PSCmdlet.WriteError( 32 | [System.Management.Automation.ErrorRecord]::new( 33 | [IO.FileNotFoundException]::new("Help files are missing!"), 34 | 'Build.HelpXmlMissing', 35 | 'ObjectNotFound', 36 | $null 37 | ) 38 | ) 39 | 40 | exit 404 41 | } 42 | 43 | $DeploymentParams = @{ 44 | Path = $Path 45 | Recurse = $false 46 | Force = $true 47 | Verbose = $true 48 | } 49 | 50 | Invoke-PSDeploy @DeploymentParams 51 | 52 | Get-ChildItem -Path $DeploymentParams['Path'] | Out-String | Write-Host 53 | 54 | $Nupkg = Get-ChildItem -Path $DeploymentParams['Path'] -Filter 'PSKoans*.nupkg' | ForEach-Object FullName 55 | 56 | $AzurePipelines = $env:BUILD_SOURCESDIRECTORY -and $env:BUILD_BUILDNUMBER 57 | $GithubActions = [bool]$env:GITHUB_WORKSPACE 58 | 59 | if ($AzurePipelines) { 60 | Write-Host "##vso[task.setvariable variable=NupkgPath]$Nupkg" 61 | } 62 | 63 | if ($GithubActions) { 64 | "NupkgPath=$Nupkg" | Add-Content -Path $env:GITHUB_ENV 65 | } 66 | -------------------------------------------------------------------------------- /PSKoans.ezformat.ps1: -------------------------------------------------------------------------------- 1 | #requires -Module EZOut 2 | # Install-Module EZOut or https://github.com/StartAutomating/EZOut 3 | $myFile = $MyInvocation.MyCommand.ScriptBlock.File 4 | $myModuleName = 'PSKoans' 5 | $myRoot = "$PSScriptRoot" 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') { 11 | Join-Path $myRoot -ChildPath $potentialDirectory | 12 | Get-ChildItem -ea ignore | 13 | Import-FormatView -FilePath { $_.Fullname } 14 | } 15 | ) 16 | 17 | $destinationRoot = "$PSScriptRoot/PSKoans" 18 | 19 | if ($formatting) { 20 | $myFormatFile = Join-Path $destinationRoot "$myModuleName.format.ps1xml" 21 | $formatting | Out-FormatData -Module $MyModuleName | Set-Content $myFormatFile -Encoding UTF8 22 | } 23 | 24 | $types = @( 25 | # Add your own Write-TypeView statements here 26 | # or declare them in the 'Types' directory 27 | Join-Path $myRoot Types | 28 | Get-Item -ea ignore | 29 | Import-TypeView 30 | 31 | ) 32 | 33 | if ($types) { 34 | $myTypesFile = Join-Path $destinationRoot "$myModuleName.types.ps1xml" 35 | $types | Out-TypeData | Set-Content $myTypesFile -Encoding UTF8 36 | } 37 | Pop-Location 38 | -------------------------------------------------------------------------------- /PSKoans/Classes/Blank.ps1: -------------------------------------------------------------------------------- 1 | class Blank { 2 | [string] ToString() { 3 | return $null 4 | } 5 | 6 | [bool] Equals([object] $other) { 7 | return $false 8 | } 9 | 10 | static [bool] op_Equality([Blank] $self, [object] $other) { 11 | return $false 12 | } 13 | 14 | static [bool] op_Inequality([Blank] $self, [object] $other) { 15 | return $true 16 | } 17 | 18 | static [bool] op_Explicit([Blank] $Instance) { 19 | return $false 20 | } 21 | 22 | static [bool] op_Implicit([Blank] $Instance) { 23 | return $false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PSKoans/Classes/KoanAttribute.ps1: -------------------------------------------------------------------------------- 1 | if ('KoanAttribute' -as [type]) { 2 | return 3 | } 4 | 5 | Add-Type -TypeDefinition @' 6 | using System; 7 | 8 | public class KoanAttribute : Attribute 9 | { 10 | public uint Position = UInt32.MaxValue; 11 | public string Module = "_powershell"; 12 | } 13 | '@ 14 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/Customization/Profile.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "PowerShell Profile", 3 | "Content": [ 4 | "When starting powershell, there is a script it will automatically execute: the PowerShell profile script.", 5 | "By default, it does not exist. It is stored in the location the variable $profile points to. By creating and adding code to it you can ensure that all your favorite aliases, variables, functions or whatnot will automatically be available each time!", 6 | "To create or open the profile file, simply run:", 7 | " notepad $profile", 8 | "\n", 9 | "The $profile variable stores the path to the 'Current User, Current Host' profile, that points to a Microsoft.PowerShell_profile.ps1 file. Other profiles can be retreived from note properties of the $profile variable:", 10 | "AllUsersAllHosts", 11 | "AllUsersCurrentHost", 12 | "CurrentUserAllHosts", 13 | "CurrentUserCurrentHost", 14 | "\n", 15 | "Different PSHosts (applications hosting a powershell process) like IDEs have custom profile paths that are referenced by $profile. While the default $profile will be available in every environment, it is useful to know where IDEs save their profiles. Customizations only required for e.g. development can be added.", 16 | "Powershell ISE uses $profile, but with a custom path pointing to a Microsoft.PowerShellISE_profile.ps1 file.", 17 | "Visual Studio Code uses $profile, but with a custom path pointing to a Microsoft.VSCode_profile.ps1 file.", 18 | "The $profile variable stores by default the path to the 'Current User, Current Host' profile even in IDEs." 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/Fun/FunWithCats.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Fun with Cat Facts", 3 | "Content": [ 4 | "PowerShell doesn't have to all be about Server administration, ops, or automation.", 5 | "\n", 6 | "It can also be fun!", 7 | "\n", 8 | "We've provided some fun examples below just to give you a taste of what PowerShell can do.", 9 | "\n", 10 | "Cats", 11 | "\n", 12 | "The below code snippet will query a REST API on the internet that provides some fun facts about cats.", 13 | "It then uses text-to-speech to tell you these facts! Feel free to run it on your machine (Windows only):", 14 | "\n", 15 | "function Get-CatFact {", 16 | "\t$SpeechSynth = New-Object -ComObject SAPI.SpVoice", 17 | "\t$URI = 'https://catfact.ninja/fact'", 18 | "\t$CatFact = Invoke-RestMethod -Uri $URI | Select-Object -ExpandProperty fact", 19 | "\t$SpeechSynth.Speak(\"Did you know? $CatFact\") | Out-Null", 20 | "}", 21 | "\n", 22 | "Get-CatFact" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/Fun/FunWithGUIs.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Fun with GUIs", 3 | "Content": [ 4 | "PowerShell doesn't have to all be about Server administration, ops, or automation.", 5 | "It can also be fun!", 6 | "\n", 7 | "We've provided some fun examples below just to give you a taste of what PowerShell can do", 8 | "\n", 9 | "GUIs", 10 | "\n", 11 | "\tThe below code snippet will make a blue GUI. When you're more experienced you may", 12 | "\twant to start making GUIs for your scripts. This is just a quick example.", 13 | "\n", 14 | "\tCopy the below into PowerShell ISE to create the GUI!", 15 | "\n", 16 | "# Import Assemblies ", 17 | "[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Drawing')", 18 | "[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')", 19 | "\n", 20 | "# Create a new form object and assign it to the variable GUIExample", 21 | "$GUIExample = New-Object System.Windows.Forms.Form", 22 | "\n", 23 | "# Define the background color and set the form width/height", 24 | "$GUIExample.BackColor = [System.Drawing.Color]::Blue", 25 | "$GUIExample.Width = 200", 26 | "$GUIExample.Height = 400", 27 | "\n", 28 | "# Make the GUI the topmost window and give it focus (make it the selected window)", 29 | "$GUIExample.TopMost = $True", 30 | "$GUIExample.Add_Shown({$GUIExample.Activate()})", 31 | "\n", 32 | "# Show the GUI", 33 | "[void]$GUIExample.ShowDialog()" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/Interactive/CompletionOptions.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Browsing Completion Options", 3 | "Content": [ 4 | "When typing a parameter or the value for a parameter, you can hit the TAB key to cycle through the options available. While this is generally well known, it is LESS well known, that by hitting \"CTRL\"+\"SpaceBar\" you can show all available options at once and use the cursor keys to navigate to the option you want." 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/Interactive/EditMode.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Edit Mode", 3 | "Content": [ 4 | "The console experience between Linux and Windows is very different. In PowerShell, if you are crossing the boundary, you can still have a lot of the behavior you are used to, using Set-PSReadlineOption.", 5 | " Set-PSReadlineOption -EditMode Emacs", 6 | " Set-PSReadlineOption -EditMode Vi", 7 | " Set-PSReadlineOption -EditMode Windows" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /PSKoans/Data/Advice/OOP/OOPIntroduction.Advice.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Object-Orientated Programming (OOP) Introduction", 3 | "Content": [ 4 | "PowerShell is an object-oriented shell, but what does this mean? Everything is an object!", 5 | "\n", 6 | "Objects are a way for us to build programs to scale, and OOP is standard in software development. There are three fundamentals you need to know:", 7 | "\n", 8 | "\t1. Objects have defined types, which are like blueprints", 9 | "\t2. Objects store data in properties", 10 | "\t3. Objects have methods which can be called to perform an action", 11 | "\n", 12 | "If you've already completed the Get-Member topic, then you'll know we can use this cmdlet to discover what blueprint an object is using, what properties it has, and what methods it may execute. For example, we may take a string and pass it into Get-Member in order to get the below result:", 13 | "\n", 14 | "\t\tTypeName: System.String", 15 | "\n", 16 | "\tName MemberType Definition", 17 | "\t---- ---------- ----------", 18 | "\tClone Method System.Object Clone(), System.Object ICloneable.Clone()", 19 | "\tLength Property int Length {get;}", 20 | "\n", 21 | "Which shows us the object type (System.String), as well as some properties and methods.", 22 | "\n", 23 | "For more information please see the below articles on OOP concepts:", 24 | "\n", 25 | "\t Abstraction: https://stackify.com/oop-concept-abstraction/", 26 | "\t Encapsulation: https://stackify.com/oop-concept-for-beginners-what-is-encapsulation/", 27 | "\t Inheritance: https://stackify.com/oop-concept-inheritance/", 28 | "\t Polymorphism: https://stackify.com/oop-concept-polymorphism/", 29 | "\n", 30 | "The .NET Core API browser may also be useful given that .NET is the foundation on which PowerShell is built: https://docs.microsoft.com/en-us/dotnet/api/?view=netcore-3.0" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /PSKoans/Dependencies.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Pester = '4.3.1' 3 | } 4 | -------------------------------------------------------------------------------- /PSKoans/DummyTypes.ps1: -------------------------------------------------------------------------------- 1 | # Only attempt to load dummy types if they're not already present in the session 2 | 3 | switch ($null) { 4 | ('FillerType' -as [type]) { 5 | class FillerType { } 6 | } 7 | ('__' -as [type]) { 8 | class __ : FillerType { } 9 | } 10 | ('____' -as [type]) { 11 | class ____ : FillerType { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PSKoans/Init/AddPesterAssertionOperator.ps1: -------------------------------------------------------------------------------- 1 | try { 2 | Add-AssertionOperator -Name Fail -Test { 3 | param ($ActualValue, [switch] $Negate, [string] $Because) 4 | 5 | if ($Negate) { 6 | return [PSCustomObject]@{ 7 | Succeeded = $false 8 | FailureMessage = "Should -Not -Fail is not a valid assertion." 9 | } 10 | } 11 | 12 | if (-not $Because) { 13 | $Message = "The test failed, because the script reached the Should -Fail command call." 14 | } 15 | else { 16 | $Reason = ($Because.Trim().TrimEnd('.') -replace '^because\s', '') 17 | $Message = "The test failed, because $Reason." 18 | } 19 | 20 | [PSCustomObject]@{ 21 | Succeeded = $false 22 | FailureMessage = $Message 23 | } 24 | } 25 | } 26 | catch { } 27 | -------------------------------------------------------------------------------- /PSKoans/Init/ModuleConfiguration.ps1: -------------------------------------------------------------------------------- 1 | Write-Verbose 'Configuring PSKoans module' 2 | Write-Verbose 'Importing data strings' 3 | 4 | $ShowMeditationPromptData = Import-PowerShellDataFile -Path "$script:ModuleRoot/Data/Show-MeditationPrompt.Data.psd1" 5 | $script:MeditationStrings = $ShowMeditationPromptData['Koans'] 6 | Remove-Variable -Name 'ShowMeditationPromptData' 7 | 8 | $Settings = Get-PSKoanSetting 9 | 10 | Write-Information "Koans folder set to $($Settings.KoanLocation)" 11 | 12 | if (-not (Test-Path -Path $Settings.KoanLocation)) { 13 | Write-Information "Koans folder '$($Settings.KoanLocation)' was not found; creating koans directory." 14 | Update-PSKoan -Confirm:$false 15 | } 16 | -------------------------------------------------------------------------------- /PSKoans/Init/RegisterArgumentCompleters.ps1: -------------------------------------------------------------------------------- 1 | $CommandName = @( 2 | 'Get-Karma' 3 | 'Get-PSKoan' 4 | 'Reset-PSKoan' 5 | 'Show-Karma' 6 | 'Update-PSKoan' 7 | ) 8 | 9 | $AdviceCommandName = @( 10 | 'Show-Advice' 11 | 'Get-Advice' 12 | ) 13 | 14 | #region Topic completer 15 | 16 | $RegisterParams = @{ 17 | CommandName = $CommandName 18 | ParameterName = 'Topic' 19 | ScriptBlock = { 20 | param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) 21 | 22 | $Values = (Get-PSKoan -Scope Module -IncludeModule * -SkipAttributeParsing).Topic 23 | return @($Values) -like "$WordToComplete*" 24 | } 25 | } 26 | Register-ArgumentCompleter @RegisterParams 27 | 28 | #endregion 29 | 30 | #region Module / IncludeModule completer 31 | 32 | $RegisterParams = @{ 33 | CommandName = $CommandName 34 | ParameterName = 'Module' 35 | ScriptBlock = { 36 | param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) 37 | 38 | $Values = $script:ModuleRoot | 39 | Join-Path -ChildPath 'Koans/Modules' | 40 | Get-ChildItem -Directory | 41 | Select-Object -ExpandProperty Name 42 | 43 | return @($Values) -like "$WordToComplete*" 44 | } 45 | } 46 | Register-ArgumentCompleter @RegisterParams 47 | 48 | $RegisterParams.ParameterName = 'IncludeModule' 49 | Register-ArgumentCompleter @RegisterParams 50 | 51 | #endregion 52 | 53 | #region Name completer for *-Advice command names 54 | 55 | $RegisterParams = @{ 56 | CommandName = $AdviceCommandName 57 | ParameterName = 'Name' 58 | ScriptBlock = { 59 | param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) 60 | 61 | $Values = $script:ModuleRoot | 62 | Join-Path -ChildPath 'Data/Advice' | 63 | Get-ChildItem -File -Recurse | 64 | Select-Object @{ 65 | Name = 'Name'; 66 | Expression = { $_.BaseName.Replace('.Advice', '') } 67 | } | 68 | Select-Object -ExpandProperty Name 69 | 70 | return @($Values) -like "$WordToComplete*" 71 | } 72 | } 73 | Register-ArgumentCompleter @RegisterParams 74 | 75 | #endregion 76 | 77 | #region Setting completer 78 | 79 | $RegisterParams = @{ 80 | CommandName = 'Get-PSKoanSetting', 'Set-PSKoanSetting' 81 | ParameterName = 'Name' 82 | ScriptBlock = { 83 | param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) 84 | 85 | $Settings = & (Get-Module PSKoans) { $script:DefaultSettings } 86 | return $Settings.Keys.Where{ $_ -like "$WordToComplete*" }.ForEach{ 87 | [System.Management.Automation.CompletionResult]::new( 88 | $_, <# completionText #> 89 | $_, <# listItemText #> 90 | 'ParameterValue', <# completionResultType #> 91 | $_ <# toolTip #> 92 | ) 93 | } 94 | } 95 | } 96 | Register-ArgumentCompleter @RegisterParams 97 | 98 | #endregion 99 | -------------------------------------------------------------------------------- /PSKoans/Koans/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "intellsmi.comment-translate" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutCompareObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 204)] 3 | param() 4 | <# 5 | Compare-Object 6 | 7 | Compare-Object is primarily for comparing collections of objects rather than individual 8 | items, and is usually used to display data comparisons to the user. 9 | #> 10 | Describe 'Compare-Object' { 11 | 12 | It 'compares collections of objects' { 13 | $Comparison = @{ 14 | ReferenceObject = 'this', 'is', 'the', 'reference' 15 | DifferenceObject = 'these', 'is', 'the', 'difference' 16 | } 17 | $CompareData = Compare-Object @Comparison 18 | '__' | Should -Be $CompareData[0].InputObject 19 | '__' | Should -Be $CompareData[0].SideIndicator 20 | $CompareData[0] | Should -BeOfType __ 21 | } 22 | 23 | It 'can display items that occur in both sets' { 24 | # By default, items that are the same in both collections are hidden. 25 | $Comparison = @{ 26 | ReferenceObject = 'this', 'is', 'the', 'reference' 27 | DifferenceObject = 'these', 'is', 'the', 'difference' 28 | } 29 | $CompareData = Compare-Object @Comparison -IncludeEqual 30 | $CompareData.SideIndicator -contains '==' | Should -BeTrue 31 | '__' | Should -Be $CompareData.Where{$_.InputObject -eq 'the'}.SideIndicator 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutForEachObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 208)] 3 | param() 4 | <# 5 | ForEach-Object 6 | 7 | As the name implies, ForEach-Object acts like a loop, similar to the native 8 | foreach keyword. 9 | 10 | It can be used at any point in a pipeline context to perform custom actions 11 | not otherwise performed by existing cmdlets. 12 | 13 | ForEach-Object optionally takes parameters for Begin and End script blocks, 14 | allowing it to become a sort of impromptu advanced function of sorts. 15 | #> 16 | Describe 'ForEach-Object' { 17 | 18 | It 'is similar to foreach' { 19 | $ForEachLoop = foreach ($Number in 1..10) { 20 | "The number is $Number!" 21 | } 22 | 23 | $ForEachObject = 1..10 | ForEach-Object { 24 | "__" 25 | } 26 | 27 | $ForEachLoop | Should -Be $ForEachObject 28 | } 29 | 30 | It 'takes Begin and End script blocks' { 31 | $Output = 1..5 | ForEach-Object -Begin {'__'} -Process {$_} -End {'END'} 32 | 33 | $Output[0] | Should -Be 'BACON' 34 | '__' | Should -Be $Output[-1] 35 | } 36 | 37 | It 'can iterate over only a specific property of the objects' { 38 | $Strings = 'hello', 'goodbye', 'what', 'what', 'you first' 39 | $Strings | ForEach-Object -MemberName __ | Should -Be @(5, 7, 4, 4, 9) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutGroupObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 210)] 3 | param() 4 | <# 5 | Group-Object 6 | 7 | Group-Object can group objects based on essentially any criteria 8 | you need. Its primary focus is complex objects with properties, 9 | but it's perfectly possible to employ custom groupings on any 10 | data set. 11 | #> 12 | Describe 'Group-Object' { 13 | BeforeAll { 14 | $Files = Get-ChildItem -Path $home -Recurse -Depth 2 -File 15 | } 16 | 17 | It 'groups items based on specified properties' { 18 | $Extensions = $Files | Group-Object -Property Extension 19 | <# 20 | The structure of the grouped objects is as follows: 21 | Count (Number of items in this group) 22 | Name (Property that they are grouped by) 23 | Group (All the objects in the group) 24 | #> 25 | '____' | Should -Be $Extensions[3].Name 26 | __ | Should -Be $Extensions[2].Count 27 | } 28 | 29 | It 'can group on any custom expression' { 30 | $NameLengths = $Files | 31 | Group-Object -Property {$_.Name.Length} | 32 | Sort-Object -Property {[int]$_.Name} # The 'name' here is the group name 33 | 34 | $ShortestFileNames = $NameLengths[0].Group.Name 35 | __ | Should -Be $ShortestFileNames.Length 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutMeasureObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 206)] 3 | param() 4 | <# 5 | Measure-Object 6 | 7 | Measure-Object is, as its name implies, a utility cmdlet that 8 | performs mathematical operations on collections of objects. 9 | #> 10 | Describe 'Measure-Object' { 11 | BeforeAll { 12 | $Numbers = @( 13 | 839, 339, 763, 663, 238, 427, 577, 613, 284, 453 14 | 850, 130, 250, 843, 669, 972, 572, 41, 172, 155 15 | 729, 616, 285, 231, 128, 540, 204, 584, 407, 98 16 | 668, 85, 320, 435, 87, 719, 936, 25, 75, 122 17 | 665, 154, 943, 35, 391, 816, 420, 229, 3, 938 18 | ) 19 | 20 | $Files = Get-ChildItem -Path $HOME -Recurse -Depth 2 21 | } 22 | 23 | It 'can count objects' { 24 | $Numbers | 25 | Measure-Object | 26 | Select-Object -ExpandProperty Count | 27 | Should -Be __ 28 | 29 | $Files | 30 | Measure-Object | 31 | Select-Object -ExpandProperty Count | 32 | Should -Be __ 33 | } 34 | 35 | It 'can sum numerical objects' { 36 | $StopIndex = __ 37 | 38 | $Numbers[0..$StopIndex] | 39 | Measure-Object -Sum | 40 | Select-Object -ExpandProperty Sum | 41 | Should -Be 6046 42 | } 43 | 44 | It 'can average numerical objects' { 45 | $StartIndex = __ 46 | 47 | $Numbers[$StartIndex..25] | 48 | Measure-Object -Average | 49 | Select-Object -ExpandProperty Average | 50 | Should -Be 421.5 51 | } 52 | 53 | It 'can find the largest or smallest value' { 54 | $StopIndex = __ 55 | 56 | $Values = $Numbers[5..$StopIndex] | 57 | Measure-Object -Minimum -Maximum 58 | 59 | $Values.Minimum | Should -Be 130 60 | $Values.Maximum | Should -Be 972 61 | } 62 | 63 | It 'can find multiple values at once' { 64 | $StartIndex = __ 65 | $StopIndex = __ 66 | 67 | $Values = $Numbers[$StartIndex..$StopIndex] | 68 | Measure-Object -Average -Minimum -Sum 69 | 70 | $Values.Average | Should -Be 377 71 | # 'Count' is always present in the data from Measure-Object 72 | $Values.Count | Should -Be 11 73 | $Values.Minimum | Should -Be 85 74 | $Values.Sum | Should -Be 4147 75 | } 76 | 77 | It 'can operate on object properties' { 78 | $Data = $Files.BaseName | Measure-Object -Property Length -Sum -Average 79 | 80 | # Averages can have a lot of decimal places, so we'll round to just 4 decimal places. 81 | $Average = [math]::Round($Data.Average, 4) 82 | 83 | __ | Should -Be $Data.Sum 84 | __ | Should -Be $Average 85 | } 86 | 87 | It 'can measure text lines, characters, and words of strings' { 88 | $Text = ' 89 | Two monks were arguing about the temple flag waving in the wind. 90 | One said, "The flag moves." 91 | The other said, "The wind moves." 92 | They argued back and forth but could not agree. 93 | 94 | Hui-neng, the sixth patriarch, said: 95 | "Gentlemen! It is not the flag that moves. It is not the wind that moves. It is your mind that moves." 96 | 97 | The two monks were struck with awe. 98 | ' 99 | $Data = $Text | Measure-Object -Line -Word -Character 100 | 101 | __ | Should -Be $Data.Lines 102 | __ | Should -Be $Data.Words 103 | __ | Should -Be $Data.Characters 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutNewObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 203)] 3 | param() 4 | <# 5 | New-Object 6 | 7 | New-Object, as the name implies, is used to create objects. For the most part, 8 | it is used to instantiate .NET objects, but can also be used to create COM 9 | objects for interactions with the Windows COM interfaces. 10 | #> 11 | Describe 'New-Object' { 12 | 13 | It 'can create objects of a specified type' { 14 | $Object = New-Object -TypeName 'System.Collections.Hashtable' 15 | $Hashtable = @{} 16 | 17 | $Hashtable | Should -BeOfType System.Collections.Hashtable 18 | $Object | Should -BeOfType __ 19 | } 20 | 21 | It 'can create objects of any available type' { 22 | $PSObject = New-Object -TypeName 'PSObject' 23 | $PSObject | Should -BeOfType __ 24 | } 25 | 26 | It 'can accept arguments for the constructor' { 27 | $Object = New-Object 'string' -ArgumentList ([char[]]@('b','a','n','a','n','a'), 0, 3) 28 | '__' | Should -Be $Object 29 | $Object | Should -BeOfType string 30 | } 31 | 32 | It 'is just one way to instantiate an object' { 33 | $Object = New-Object 'PSObject' 34 | $Object2 = [PSObject]::new() 35 | 36 | $Object | Should -BeOfType System.Management.Automation.PSObject 37 | $Object2 | Should -BeOfType __ 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutSortObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 211)] 3 | param() 4 | <# 5 | Sort-Object 6 | 7 | Sort-Object is the most common pipeline-collating cmdlet. It is extremely useful 8 | when sorting a collection of objects, and is capable of fairly sophisticated 9 | sorting methods as you require. 10 | 11 | However, note that it is a collating cmdlet; it will pause the pipeline where it 12 | is called in order to sort everything into the specified order. This can slow 13 | down processing, and as a result it is recommended to sort your collection as 14 | late in the pipeline sequence as possible. 15 | #> 16 | Describe 'Sort-Object' { 17 | BeforeAll { 18 | $Numbers = 5, 2, 7, 1, 4, 6, 8, 3, 10, 9 19 | $Strings = 'hello', 'goodbye', 'who', 'Steve', 'PowerShell' 20 | } 21 | 22 | It 'sorts a collection of objects' { 23 | $Sorted = $Numbers | Sort-Object 24 | __ | Should -Be $Sorted 25 | } 26 | 27 | It 'can sort in descending order' { 28 | $Sorted = $Strings | Sort-Object -Descending 29 | __ | Should -Be $Sorted 30 | } 31 | 32 | It 'can sort based on the properties of objects' { 33 | $Sorted = 'who', 'Steve', 'hello', 'goodbye', 'PowerShell' 34 | $Strings | Sort-Object -Property __ | Should -Be $Sorted 35 | } 36 | 37 | It 'can sort based on custom criteria' { 38 | $Sorted = $Numbers | Sort-Object {$_ % 3} 39 | __ | Should -Be $Sorted 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutTeeObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 209)] 3 | param() 4 | <# 5 | Tee-Object 6 | 7 | Tee-Object is a utility cmdlet that splits the path of pipeline 8 | objects. It directly passes through all objects that it receives, 9 | as well as storing a copy of the items either in a file or variable. 10 | #> 11 | Describe 'Tee-Object' { 12 | 13 | It 'passes the object on through the pipeline' { 14 | # The value passed to -Variable is created as a new variable 15 | $Value = 10 16 | __ | Tee-Object -Variable Tee | Should -Be $Value 17 | } 18 | 19 | It 'can store the object(s) into a variable' { 20 | # Note the variable name is given as a string, without the $ prefix. 21 | $Values = 'alpha', 'beta', 'gamma' 22 | 23 | @('____', 'beta', '____') | 24 | Tee-Object -Variable 'Numbers' | 25 | Should -Be $Values 26 | 27 | '____' | Should -Be $Numbers[0] 28 | '____' | Should -Be $Numbers[1] 29 | 'gamma' | Should -Be $Numbers[2] 30 | } 31 | 32 | It 'does not create the variable until the pipeline is completed' { 33 | $Values = 1..10 34 | 35 | $Values | 36 | Tee-Object -Variable Test | 37 | ForEach-Object { $Test | Should -BeNullOrEmpty } 38 | 39 | $Test | Should -Not -BeNullOrEmpty 40 | $____ | Should -Be $Values 41 | } 42 | 43 | It 'can also store the object(s) into a file' { 44 | <# 45 | $TestDrive, or TestDrive:, refers to a temporary location that is automatically 46 | cleaned out by Pester when the tests are concluded. 47 | #> 48 | $File = New-Item -Path "$TestDrive/TeeObjectTest.txt" 49 | 50 | 1..5 | 51 | ForEach-Object { "{0:N2}" -f (1 / $_) } | 52 | Tee-Object -FilePath $File.FullName | 53 | Out-Null 54 | 55 | $Stored = Get-Content -Path $File 56 | 57 | # Text files can only store string data, so be careful storing arbitrary data to files like this. 58 | @('__', '__', '0.33', '__', '__') | Should -Be $Stored 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PSKoans/Koans/Cmdlets 1/AboutWhereObject.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 207)] 3 | param() 4 | <# 5 | Where-Object 6 | 7 | Where-Object is a pipeline cmdlet used to filter a collection of objects and return 8 | only those that match the specified condition. 9 | #> 10 | Describe 'Where-Object' { 11 | 12 | It 'can filter value-type collections' { 13 | $Collection = @( 14 | 001, 002, 003, 004, 005, 006, 007 15 | 008, 009, 010, 011, 012, 013, 014 16 | 015, 016, 017, 018, 019, 020 17 | ) 18 | 19 | $ContainsTwo = $Collection | Where-Object {$_ -match '2'} 20 | __ | Should -Be $ContainsTwo 21 | } 22 | 23 | It 'can filter on object propertes' { 24 | $Collection = Get-ChildItem -Path $home -Recurse -Depth 2 25 | 26 | $ItemsWithNumbers = $Collection | Where-Object {$_.Name -match '\d'} 27 | __ | Should -Be $ItemsWithNumbers.Count 28 | 29 | # Pipelines also work across line breaks (as long as the pipe is at the end of the line) 30 | $Result = $ItemsWithNumbers | 31 | Select-Object -Last 1 | 32 | Select-Object -ExpandProperty Name 33 | __ | Should -Be $Result 34 | } 35 | 36 | It 'supports simplified syntax' { 37 | $Collection = Get-ChildItem -Path $home 38 | <# 39 | Most of the standard PowerShell operators are supported in this syntax. 40 | This is restricted to accessing properties by name, and will not work 41 | for nested properties. 42 | #> 43 | $Folders = $Collection | Where-Object FullName -like '*a*' 44 | 45 | $FirstTwo = $Folders.Name | Select-Object -First 2 46 | __ | Should -Be $FirstTwo 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PSKoans/Koans/Constructs and Patterns/AboutAdvancedFunctions.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 306)] 3 | param() 4 | 5 | <# 6 | Advanced Functions 7 | #> 8 | -------------------------------------------------------------------------------- /PSKoans/Koans/Constructs and Patterns/AboutCustomObjects.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 303)] 3 | param() 4 | <# 5 | Custom Objects 6 | 7 | PowerShell often makes extensive use of custom objects. Similar in usage to ExpandoObject for C#, 8 | powershell's PSObject wrapper allows almost any object to be given additional properties. These 9 | differ from their base type properties and are often in the form of NoteProperties and 10 | ScriptProperties. 11 | 12 | NoteProperties can store any value or reference to another object, permitting nested objects and 13 | effectively mirroring any static property as you might find on a defined class, but can be easily 14 | added to a new object without needing to define a class. 15 | 16 | ScriptProperties are defined as a script block value. Upon retrieving the value, the script block 17 | is evaluated, with the final value of the ScriptProperty being resolved on the completion of the 18 | script block. 19 | #> 20 | Describe '[PSCustomObject]' { 21 | It 'can be built from a hashtable' { 22 | <# 23 | This behaves like a cast, but it's actually a bit different. A directly-cast hashtable 24 | preserves the order of the elements, unlike regular hashtables, thanks to some parser 25 | magic. 26 | #> 27 | $Object = [PSCustomObject]@{ 28 | Property = 'Value' 29 | } 30 | $Object | Should -BeOfType PSCustomObject 31 | } 32 | 33 | It 'can be built by trimming objects down' { 34 | $Object = Get-ChildItem -Path $Home | Select-Object -First 1 -Property Name, Parent 35 | $Object | Should -BeOfType PSCustomObject 36 | '____' | Should -Be $Object.Parent.Name 37 | } 38 | 39 | It 'can have arbitrary properties' { 40 | $Object = [PSCustomObject]@{ '____' = 'PropertyValue' } 41 | 42 | __ | Should -Be $Object.PSObject.Properties.Count 43 | $____.PSObject.Properties.Name | Should -Be 'PropertyName' 44 | } 45 | 46 | It 'can be added to' { 47 | $Object = [PSCustomObject]@{ 'Property1' = 12 } 48 | $Object | Add-Member -MemberType NoteProperty -Name 'Property2' -Value __ 49 | 50 | $Object.Property2 | Should -Be $($Object.Property1 - 7) 51 | } 52 | 53 | It 'can have ScriptProperties' { 54 | $Object = [PSCustomObject]@{ 55 | BaseProperty = 17 56 | } 57 | 58 | $Object | Add-Member -MemberType ScriptProperty -Name DerivedProperty -Value { 59 | # ScriptProperties can reference their parent object using $this 60 | $this.BaseProperty++ 61 | $this.BaseProperty % 4 62 | } 63 | 64 | __ | Should -Be $Object.DerivedProperty 65 | # What if we call it more than once? 66 | __ | Should -Be $Object.DerivedProperty 67 | } 68 | 69 | It 'can declare ScriptProperties without Add-Member with custom getters and setters' { 70 | $Object = [PSCustomObject]@{ 71 | BaseProperty = 11 72 | } 73 | 74 | $Object.PSObject.Properties.Add( 75 | # A script property can consist of a name, a getter, and (optionally) a setter. 76 | [psscriptproperty]::new('CustomProperty', 77 | { 78 | # getter 79 | $this.BaseProperty + 1 80 | }, 81 | { 82 | # setter 83 | param($val) 84 | $this.BaseProperty = - [int]($val) 85 | } 86 | ) 87 | ) 88 | 89 | '____' | Should -Be $Object.CustomProperty 90 | $Object.CustomProperty = 12 91 | __ | Should -Be $Object.CustomProperty 92 | __ | Should -Be $Object.BaseProperty 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /PSKoans/Koans/Foundations/AboutOrderOfOperations.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 118)] 3 | param() 4 | <# 5 | Order of Operations 6 | 7 | In PowerShell, the order of operations needs to be taken into account, 8 | sometimes even more so than other languages. Because all native functions 9 | and cmdlets take space-separated arguments, any expressions needing 10 | evaluation need to be explicitly separated with parentheses in many cases. 11 | 12 | In general, PowerShell follows PEMDAS, although it has no native exponential 13 | operator. Parentheses always get evaluated first (from inner to outer if 14 | nested), but expressions cannot be passed as arguments without being forced 15 | to evaluate first. 16 | #> 17 | Describe "Order of Operations" { 18 | 19 | It "requires parameter argument expressions to be enclosed in parentheses" { 20 | function Add-Numbers { 21 | param( 22 | [int] 23 | $Number1, 24 | [int] 25 | $Number2 26 | ) 27 | return $Number1 + $Number2 28 | } 29 | # Be wary of open spaces when passing an expression as a function argument. 30 | $Sum = Add-Numbers (4 + 1) 18 31 | __ | Should -Be $Sum 32 | Add-Numbers 3 * 4 7 | Should -Be 19 # Add parentheses to the function call to make this true. 33 | } 34 | 35 | It "will evaluate an entire expression if it is the first element in a pipeline" { 36 | # A pipe character evaluates everything before it on the line before 37 | # passing along the value(s). 38 | __ + 7 | Should -Be 11 39 | __ * 3 + 11 | Should -Be 35 40 | } 41 | 42 | It "otherwise follows standard mathematical rules" { 43 | # Although PowerShell doesn't have a native exponentiation operator, 44 | # we do have [Math]::Pow($base, $power) 45 | $Value = 3 + 4 / [Math]::Pow(2, 3) 46 | __ | Should -Be $Value 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PSKoans/Koans/Foundations/AboutTypeOperators.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 119)] 3 | param() 4 | <# 5 | Type Operators 6 | 7 | -is and -isnot are used to verify the type of an object, whereas -as is used to cast or convert 8 | an object to another type entirely. 9 | 10 | For more detailed information, refer to 'Get-Help about_Type_Operators' 11 | #> 12 | Describe 'Type Operators' { 13 | 14 | Context 'Is and IsNot Operators' { 15 | 16 | It 'examines the type of the left hand object' { 17 | 45 -isnot [double] | Should -BeTrue 18 | 'string' -is [____] | Should -BeTrue 19 | } 20 | 21 | It 'is useful for determining available methods' { 22 | $Value = __ 23 | 24 | if ($Value -is [double]) { 25 | [____] | Should -Be $Value.GetType() 26 | } 27 | else { 28 | Should -Fail -Because '$Value was not the correct type.' 29 | } 30 | } 31 | } 32 | 33 | Context 'As Operator' { 34 | 35 | It 'is used to convert objects to other types' { 36 | $Value = '__' 37 | $Value | Should -BeOfType [string] 38 | 39 | $NewValue = $Value -as [int] 40 | $NewValue | Should -BeOfType int 41 | } 42 | 43 | It 'is similar to casting' { 44 | $String = '04' 45 | $Number = [int] $String 46 | $Number2 = $String -as [int] 47 | 48 | $Number | Should -Be $Number2 49 | $Number | Should -BeOfType __ 50 | } 51 | 52 | It 'does not throw errors on a failed conversion' { 53 | $Casting = { 54 | [int]'string' 55 | } 56 | $Conversion = { 57 | 'string' -as [int] 58 | } 59 | 60 | $Casting | Should -Throw -ErrorId '____' 61 | $Conversion | Should -Not -Throw 62 | __ | Should -Be $Conversion.InvokeReturnAsIs() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PSKoans/Koans/Foundations/AboutVariables.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 111)] 3 | param() 4 | <# 5 | Variables 6 | 7 | PowerShell variables are defined with a $Dollar sign and some text or numbers. 8 | Some special characters are allowed in variable names, like underscores. 9 | 10 | The PoSH Practice and Style guide recommends PascalCase for variable names 11 | when writing PowerShell code. 12 | 13 | Variables can store any value, object, or collection of objects in PowerShell. 14 | #> 15 | Describe 'Variable Assignment' { 16 | 17 | It 'gives a name to a value or object' { 18 | # Names give succinct descriptions to pieces of reality. 19 | $Fifty = 50 20 | $Value = __ 21 | 22 | Set-Variable -Name 'Greeting' -Value 'Hello!' 23 | 24 | $Value -eq $Fifty | Should -BeTrue 25 | __ | Should -Be $Greeting 26 | } 27 | 28 | <# 29 | PowerShell variables will infer the data type where possible, for 30 | example [int] for whole numbers, [double] for decimals and fractions, 31 | [string] for text. 32 | #> 33 | It 'infers types on its own' { 34 | $Number = 10 35 | 36 | $Number | Should -BeOfType [____] 37 | } 38 | 39 | It 'can directly compare types' { 40 | # For each task, a different tool. 41 | $Number = 5 42 | $Number -is [int] | Should -BeTrue 43 | $Number | Should -BeOfType [____] 44 | 45 | $Text = 'Every worthwhile step is uphill.' 46 | [____] | Should -Be $Text.GetType() 47 | } 48 | 49 | It 'allows types to be explicitly set' { 50 | # A well-defined container decides the shape of its contents. 51 | [int]$Number = '42' 52 | 53 | <# 54 | An unrestricted container may hold many different items. 55 | Its contents may choose their own kind, or it be chosen for them. 56 | #> 57 | $String = [string]$true 58 | 59 | [____] | Should -Be $Number.GetType() 60 | $String | Should -BeOfType [____] 61 | } 62 | 63 | It 'distinguishes between types of numbers' { 64 | <# 65 | There are many kinds of numbers in PowerShell, but the basics are 66 | [int] and [double] for integers and floating-point numbers 67 | #> 68 | 69 | $Integer = 100 70 | $NotInteger = 12.0 71 | 72 | $Integer | Should -BeOfType [int] 73 | $NotInteger | Should -BeOfType [____] 74 | } 75 | 76 | It 'allows you to declare constant variables' { 77 | $RemoveConstant = { 78 | # Constant variables cannot be altered. 79 | Set-Variable -Name 'Constant' -Value 25 -Option Constant 80 | 81 | # So what happens if we try to modify $Constant in some way? 82 | $____ = 'NewValue' 83 | } 84 | $RemoveConstant | Should -Throw -ExpectedMessage '____' 85 | 86 | $RemoveReadOnly = { 87 | # Contrast Read-Only variables, which can be later removed (if you do it right.) 88 | Set-Variable -Name 'Constant' -Value 25 -Option ReadOnly 89 | 90 | # While these variables can be Removed, they cannot be directly altered. 91 | Remove-Variable -Name '____' -Force -ErrorAction Stop 92 | 93 | # Once the readonly variable is removed, we can re-create and edit it as normal. 94 | $Constant = 2 95 | $Constant++ 96 | } 97 | $RemoveReadOnly | Should -Not -Throw 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PSKoans/Koans/Introduction/AboutAssertions.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 101)] 3 | param() 4 | <# 5 | Getting Started 6 | 7 | The PowerShell Koans are a set of exercises designed to get you familiar 8 | with PowerShell. By the time you're done, you'll have a basic understanding 9 | of the syntax of PowerShell and learn a little more about scripting. This is 10 | where the fun begins! Each koan contains an example designed to teach you 11 | something about PowerShell. 12 | 13 | Solving Problems 14 | 15 | If you execute the `Show-Karma` command in your PowerShell console, you will 16 | get a message that one of the assertions below has failed. Your job is to 17 | fill in the blanks (the __ / ____ / $____ / '____' tokens) to make it pass. 18 | 19 | Once you make the change(s) necessary and save the file, call `Show-Karma` 20 | to make sure the koan passes, and continue on to the next failing koan. With 21 | each passing koan, you'll learn more about PowerShell, and add another tool 22 | to your PowerShell scripting belt. 23 | #> 24 | Describe 'Equality' { 25 | 26 | It 'is a simple comparison' { 27 | # Some truths are absolute. 28 | $____ | Should -Be $true 29 | } 30 | 31 | It 'expects you to fill in values' { 32 | # Initiative will be rewarded. 33 | __ | Should -Be (1 + 2) 34 | __ + 5 | Should -Be 10 35 | } 36 | 37 | It 'sets the expectations' { 38 | # Many roads converge, yet some paths are less clear. 39 | $ExpectedValue = 1 + 1 40 | $ActualValue = __ 41 | 42 | $ExpectedValue -eq $ActualValue | Should -BeTrue 43 | } 44 | 45 | # Easy, right? Try one more! 46 | 47 | It 'demands balance' { 48 | # Both sides of the scale must be of equal measure. 49 | __ + 2 | Should -Be 3 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PSKoans/Koans/Introduction/AboutBooleans.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | [Koan(Position = 102)] 3 | param() 4 | <# 5 | Booleans 6 | 7 | Booleans in PowerShell are either $true or $false, 1 or 0, on or off. They 8 | allow us to represent a logical, binary, state. Effectively, they represent 9 | the state of a single bit of data. For example: 10 | 11 | "Can I stop this service?" 12 | 13 | The answer is yes or no, True or False. 14 | 15 | One example of a boolean property would be the Responding property on a 16 | System.Diagnostics.Process object (which is the output from Get-Process). 17 | This property can only show whether the process is responding (True) or not 18 | (False). 19 | 20 | Check for yourself by running the following code: 21 | 22 | Get-Process | Get-Member 23 | 24 | Then scroll down till you see the "Responding" property: 25 | 26 | Responding Property bool Responding {get;} 27 | #> 28 | 29 | Describe "Booleans" { 30 | <# 31 | Fill in the blanks below with either $true or $false, indicating the 32 | result you expect from the noted expressions. 33 | 34 | We'll cover comparison operators in more detail later on, but here's a 35 | summary of the operators used here: 36 | 37 | -gt => "is greater than" 38 | -eq => "is equal to" 39 | -lt => "is less than" 40 | 41 | As an example, the expression 7 -gt 5 asserts "7 is greater than 5". 42 | This is a true statement, so the final value of the expression is $true. 43 | #> 44 | 45 | It 'evaluates ( 1 -gt 2 ) as a boolean expression' { 46 | $____ | Should -Be ( 1 -gt 2 ) -Because '1 is not greater than 2' 47 | } 48 | 49 | It 'evaluates ( 1 -lt 2 ) as a boolean expression' { 50 | $____ | Should -Be ( 1 -lt 2 ) -Because '1 is less than 2' 51 | } 52 | 53 | It 'evaluates ( 10 -lt 20 ) as a boolean expression' { 54 | $____ | Should -Be ( 10 -lt 20 ) -Because '10 is less than 20' 55 | } 56 | 57 | It 'evaluates ( 10 -gt 20 ) as a boolean expression' { 58 | $____ | Should -Be ( 10 -gt 20 ) -Because 'The lesser is not greater' 59 | } 60 | 61 | It 'evaluates ( 3 -eq 3 ) as a boolean expression' { 62 | $____ | Should -Be ( 3 -eq 3 ) -Because 'A mirror reflects true' 63 | } 64 | 65 | It 'evaluates ( 100 -lt 1 ) as a boolean expression' { 66 | $____ | Should -Be ( 100 -lt 1 ) -Because '100 is not less than 1' 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PSKoans/Koans/Katas/SortingCharacters.Koans.ps1: -------------------------------------------------------------------------------- 1 | using module PSKoans 2 | using namespace System.Collections.Generic 3 | using namespace System.Management.Automation.Language 4 | [Koan(Position = 151)] 5 | param() 6 | 7 | <# 8 | Kata: Sorting Characters 9 | 10 | Sorting is a fairly common task in programming. In PowerShell, the Sort-Object cmdlet will typically 11 | be used to sort items. 12 | 13 | For the purposes of this kata, cmdlets will be removed from your toolkit. 14 | 15 | Using only native PowerShell expressions (values, operators, and keywords), implement a function that 16 | sorts all characters in a string alphabetically. Punctuation and spaces should be ignored entirely. 17 | #> 18 | Describe 'Kata - Sorting Characters' { 19 | 20 | BeforeAll { 21 | $Verification = { 22 | $Functions = [Hashset[string]]::new([StringComparer]::OrdinalIgnoreCase) 23 | $Ast = (Get-Command 'Get-SortedString' -CommandType Function).ScriptBlock.Ast 24 | $Ast.FindAll( 25 | { 26 | param($node) 27 | if ($node -is [CommandAst] -and ($name = $node.GetCommandName()) -and !$Functions.Contains($name)) { 28 | throw 'Usage of external cmdlets and functions is not permitted.' 29 | } 30 | 31 | if ($node -is [FunctionDefinitionAst]) { 32 | $Functions.Add($node.Name) > $null 33 | return 34 | } 35 | 36 | if ($node.Left -is [VariableExpressionAst] -and $node.Left.VariablePath.DriveName -eq 'Function') { 37 | $Functions.Add($node.Left.VariablePath.UserPath -replace '^function:') > $null 38 | } 39 | }, $true 40 | ) 41 | } 42 | 43 | function Get-SortedString { 44 | param($String) 45 | 46 | # Add your solution code here! 47 | 48 | } 49 | } 50 | 51 | <# 52 | Feel free to add extra It/Should tests here to write additional tests for yourself and 53 | experiment along the way! 54 | #> 55 | 56 | It 'should not use any cmdlets or external commands' { 57 | $Verification | Should -Not -Throw -Because 'You must rely on your own knowledge here.' 58 | } 59 | 60 | It 'should give the right result when inputting: ' { 61 | param( $String, $Result ) 62 | 63 | Get-SortedString $String | Should -BeExactly $Result 64 | } -TestCases @( 65 | @{ 66 | String = 'Mountains are merely mountains.' 67 | Result = 'aaaeeeiilmmMnnnnoorrssttuuy' 68 | } 69 | @{ 70 | String = 'What do you call the world?' 71 | Result = 'aacddehhlllooorttuwWy' 72 | } 73 | @{ 74 | String = 'Out of nowhere, the mind comes forth.' 75 | Result = 'cdeeeeffhhhimmnnooooOrrstttuw' 76 | } 77 | @{ 78 | String = 'Because it is so very clear, it takes longer to come to the realization.' 79 | Result = 'aaaaaBccceeeeeeeeeghiiiiiklllmnnoooooorrrrsssstttttttuvyz' 80 | } 81 | @{ 82 | String = 'The hands of the world are open.' 83 | Result = 'aaddeeeefhhhlnnoooprrstTw' 84 | } 85 | @{ 86 | String = 'You are those huge waves sweeping everything before them, swallowing all in their path.' 87 | Result = 'aaaaabeeeeeeeeeeeefgggghhhhhhiiiiillllmnnnnoooopprrrrsssstttttuuvvwwwwyY' 88 | } 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/PSKoans/Koans/Modules/.gitkeep -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/AboutDbaDatabase.Koans.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules dbatools 2 | using module PSKoans 3 | [Koan(Position = 1001, Module = 'dbatools')] 4 | param() 5 | <# 6 | Get-DbaDatabase 7 | 8 | The Get-DbaDatabase command gets SQL database information for each database 9 | that is present on the target instance(s) of SQL Server by default. If the 10 | name of the database is provided, the command will return only the specific 11 | database. 12 | #> 13 | Describe 'Get-DbaDatabase' { 14 | 15 | #region Mocks 16 | <# 17 | Let's setup the environment for you. Unless you want your Koans to 18 | nearly always fail I'd suggest not messing with this bit. 19 | #> 20 | BeforeAll { 21 | Mock -CommandName Get-DbaDatabase -MockWith { 22 | Import-Clixml -Path .\PSKoans\Koans\dbatools\Mocks\Database_All.xml 23 | } -ParameterFilter { $_.SqlInstance -eq 'localhost' } 24 | 25 | Mock -CommandName Get-DbaDatabase -MockWith { 26 | Import-Clixml -Path .\PSKoans\Koans\dbatools\Mocks\Database_TestDb.xml 27 | } -ParameterFilter { $_.SqlInstance -eq 'localhost' -and $_.Database -eq 'testdb' } 28 | 29 | Mock -CommandName Get-DbaDatabase -MockWith { 30 | Import-Clixml -Path .\PSKoans\Koans\dbatools\Mocks\Database_System.xml 31 | } -ParameterFilter { $_.SqlInstance -eq 'localhost' -and $_.ExcludeUser } 32 | 33 | Mock -CommandName Get-DbaDatabase -MockWith { 34 | Import-Clixml -Path .\PSKoans\Koans\dbatools\Mocks\Database_TestDb.xml 35 | } -ParameterFilter { $_.SqlInstance -eq 'localhost' -and $_.ExludeSystem } 36 | } 37 | #endregion 38 | 39 | It 'Gathers databases by SQL Server instance...' { 40 | <# 41 | Get-DbaDatabase requires one thing; A SQL Server instance name. 42 | You can pass in "localhost" for the default name for a SQL Server 43 | instance. 44 | The simplest usage of Get-DbaDatabase is to run it and passing in the 45 | name of the SQL Server instance. This will get information about all 46 | the databases on the instance. 47 | #> 48 | $AllDatabases = Get-DbaDatabase -SqlInstance ____ 49 | $AllDatabases.Count | Should -Be 5 50 | } 51 | 52 | It 'Gathers database by SQL Server instance and specific name...' { 53 | <# 54 | By passing in the SQL Server instance and the name of a specific 55 | database, using the -Database parameter, we can get information on 56 | that single database instead. 57 | #> 58 | $primaryDatabase = Get-DbaDatabase -SqlInstance localhost -Database ____ 59 | $primaryDatabase.Name | Should -Be 'testdb' 60 | } 61 | 62 | It 'Gathers system databases only if specified...' { 63 | <# 64 | You may want to get only the system databases on an instance. 65 | While you can pass in the specific names of the system databases, it 66 | would be easier if there was a parameter you could add that would 67 | return the system databases. 68 | 69 | A switch parameter like -ExcludeUser. 70 | #> 71 | $UserDbParams = @{ 72 | SqlInstance = 'localhost' 73 | ExcludeUser = $____ 74 | } 75 | $UserDbsExcluded = Get-DbaDatabase @UserDbParams 76 | $UserDbsExcluded.Name | Should -BeIn 'tempdb', 'primary', 'model', 'msdb' 77 | } 78 | 79 | It 'Excludes system databases if specified...' { 80 | <# 81 | The same can be done to exclude system databases by providing the 82 | -ExcludeSystem parameter switch. 83 | #> 84 | $SystemDbParams = @{ 85 | SqlInstance = 'localhost' 86 | ExludeSystem = ____ 87 | } 88 | $SystemDbsExluded = Get-DbaDatabase @SystemDbParams 89 | $SystemDbsExluded.Name | Should -Be 'testdb' 90 | } 91 | 92 | It 'Gathers databases based on their recovery model...' { 93 | <# 94 | Some common questions that people who work with databases 95 | may have getting databases that are in the 'Full', 'Simple', or 96 | 'BulkLogged' recovery models. 97 | #> 98 | $FullRecoveryDbs = Get-DbaDatabase -SqlInstance localhost -RecoveryModel ____ 99 | $FullRecoveryDbs.RecoveryModel | Should -Be 'Full' 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/BasicInvokeDbaQuery.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Data.DataRow 5 | System.Object 6 | 7 | System.Data.DataRow 8 | 9 | database_name 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/Database_System.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Deserialized.System.Management.Automation.PSCustomObject 5 | Deserialized.System.Object 6 | 7 | 8 | Server01 9 | MSSQLSERVER 10 | Server01 11 | tempdb 12 | Normal 13 | true 14 | Full 15 | Nothing 16 | 46.13 17 | Version120 18 | Latin1_General_CI_AS 19 | sa 20 |
2019-02-26T19:57:35
21 |
2019-02-26T13:03:43
22 |
2019-02-26T20:02:57
23 |
24 |
25 | 26 | 27 | 28 | Server01 29 | MSSQLSERVER 30 | Server01 31 | primary 32 | Normal 33 | true 34 | Full 35 | Nothing 36 | 29.56 37 | Version120 38 | Latin1_General_CI_AS 39 | sa 40 |
2019-02-26T19:59:39
41 |
2019-02-26T13:01:29
42 |
2019-02-26T20:04:34
43 |
44 |
45 | 46 | 47 | 48 | Server01 49 | MSSQLSERVER 50 | Server01 51 | model 52 | Normal 53 | true 54 | Full 55 | Nothing 56 | 39.68 57 | Version120 58 | Latin1_General_CI_AS 59 | sa 60 |
2019-02-26T20:01:03
61 |
2019-02-26T13:02:30
62 |
2019-02-26T20:01:19
63 |
64 |
65 | 66 | 67 | 68 | Server01 69 | MSSQLSERVER 70 | Server01 71 | msdb 72 | Normal 73 | true 74 | Full 75 | Nothing 76 | 79.29 77 | Version120 78 | Latin1_General_CI_AS 79 | sa 80 |
2019-02-26T19:59:54
81 |
2019-02-26T13:05:07
82 |
2019-02-26T20:04:01
83 |
84 |
85 |
86 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/Database_TestDb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Management.Automation.PSCustomObject 5 | System.Object 6 | 7 | 8 | Server01 9 | MSSQLSERVER 10 | Server01 11 | testdb 12 | Normal 13 | true 14 | Full 15 | LogBackup 16 | 95.275 17 | Version120 18 | Latin1_General_CI_AS 19 | sa 20 |
2019-02-26T19:56:36
21 |
2019-02-26T13:00:31
22 |
2019-02-26T20:00:02
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/MultipleServerQuery.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Management.Automation.PSCustomObject 5 | System.Object 6 | 7 | 8 | localhost 9 | 10 | 11 | 12 | 13 | 14 | localhost\SQLDEV2K14 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/StudentTable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Data.DataRow 5 | System.Object 6 | 7 | System.Data.DataRow 8 | 9 | Bob 10 | 11 | 12 | 13 | 14 | System.Data.DataRow 15 | 16 | Robert 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/StudentTableBobbySafe.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Data.DataRow 5 | System.Object 6 | 7 | System.Data.DataRow 8 | 9 | Bob 10 | 11 | 12 | 13 | 14 | System.Data.DataRow 15 | 16 | Robert 17 | 18 | 19 | 20 | 21 | System.Data.DataRow 22 | 23 | Robert');-- DROP TABLE Student 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PSKoans/Koans/Modules/dbatools/Mocks/StudentTableParams.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Data.DataRow 5 | System.Object 6 | 7 | System.Data.DataRow 8 | 9 | Bob 10 | 11 | 12 | 13 | 14 | System.Data.DataRow 15 | 16 | Robert 17 | 18 | 19 | 20 | 21 | System.Data.DataRow 22 | 23 | Robert'); DROP TABLE Students;-- 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PSKoans/PSKoans.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Provides a library of editable Pester tests to assist learning PowerShell. 4 | .DESCRIPTION 5 | Provides a simplified interface with Pester tests that describe core PowerShell 6 | functions, language features, and techniques, in order to facilitate quickly 7 | learning the PowerShell language. 8 | .EXAMPLE 9 | PS> Measure-Karma 10 | 11 | Execute the recorded tests to check your progress. 12 | .NOTES 13 | Author: Joel Sallow 14 | #> 15 | 16 | $script:ModuleRoot = $PSScriptRoot 17 | $script:ConfigPath = '~/.config/PSKoans/config.json' 18 | $script:DefaultSettings = @{ 19 | KoanLocation = Resolve-Path -Path '~' | Join-Path -ChildPath 'PSKoans' 20 | Editor = 'code' 21 | } 22 | 23 | [hashtable] $script:CurrentTopic = $null 24 | [runspace] $script:KoanRunspace = $null 25 | 26 | #region SupportingClasses 27 | 28 | Get-ChildItem -Path "$PSScriptRoot/Classes" | ForEach-Object { 29 | Write-Verbose "Importing classes from file: [$($_.Name)]" 30 | . $_.FullName 31 | } 32 | 33 | #endregion SupportingClasses 34 | 35 | #region ImportCommands 36 | 37 | Get-ChildItem -Path "$PSScriptRoot/Public", "$PSScriptRoot/Private" | ForEach-Object { 38 | Write-Verbose "Importing functions from file: [$($_.Name)]" 39 | . $_.FullName 40 | } 41 | 42 | #endregion ImportCommands 43 | 44 | #region Initialization 45 | Get-ChildItem -Path "$PSScriptRoot/Init" | ForEach-Object { 46 | Write-Verbose "Initializing module: [$($_.Name)]" 47 | . $_.FullName 48 | } 49 | -------------------------------------------------------------------------------- /PSKoans/Private/Assert-UnblockedFile.ps1: -------------------------------------------------------------------------------- 1 | function Assert-UnblockedFile { 2 | <# 3 | .SYNOPSIS 4 | Asserts that the file is not a file is blocked. 5 | 6 | .DESCRIPTION 7 | Files may be blocked when copied from Internet zones on Windows. Blocked files will have an alternate NTFS 8 | stream named Zone.Identifier. 9 | 10 | This command raises a terminating error is the file at the specified path is blocked. 11 | 12 | This check is skipped if the platform is not Windows or the file does not exist. 13 | 14 | .PARAMETER FileInfo 15 | An instance of System.IO.FileInfo to test. 16 | 17 | .INPUTS 18 | System.IO.FileInfo 19 | 20 | #> 21 | 22 | [CmdletBinding()] 23 | [OutputType([System.IO.FileInfo])] 24 | param( 25 | [Parameter(Mandatory, ValueFromPipeline)] 26 | [System.IO.FileInfo] 27 | $FileInfo, 28 | 29 | [Switch] 30 | $PassThru 31 | ) 32 | 33 | process { 34 | if ($PSVersionTable.PSEdition -ne 'Desktop' -and $PSVersionTable.Platform -ne 'Win32NT') { 35 | if ($PassThru) { 36 | return $FileInfo 37 | } 38 | } 39 | 40 | if (-not $FileInfo.Exists) { 41 | if ($PassThru) { 42 | return $FileInfo 43 | } 44 | } 45 | 46 | if (Get-Content -Path $FileInfo.FullName -Stream Zone.Identifier -ErrorAction Ignore) { 47 | $ErrorDetails = @{ 48 | ExceptionType = 'System.IO.FileLoadException' 49 | ExceptionMessage = 'Could not read the koan file "{0}". The file is blocked and may have been copied from an Internet location. Use the Unblock-File to remove the block on the file.' -f $FileInfo.FullName 50 | ErrorId = 'PSKoans.KoanFileIsBlocked' 51 | ErrorCategory = 'ReadError' 52 | TargetObject = $FileInfo 53 | } 54 | $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) 55 | } else { 56 | if ($PassThru) { 57 | return $FileInfo 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /PSKoans/Private/ConvertFrom-WildcardPattern.ps1: -------------------------------------------------------------------------------- 1 | using namespace System.Text 2 | using namespace System.Management.Automation 3 | 4 | function ConvertFrom-WildcardPattern { 5 | <# 6 | .SYNOPSIS 7 | Returns a regular expression from an array of wildcard patterns. 8 | 9 | .DESCRIPTION 10 | Returns a regular expression from an array of wildcard patterns using PowerShell's WildcardPatternToRegexParser class. 11 | 12 | .PARAMETER Pattern 13 | Zero or more wildcard patterns to convert. 14 | 15 | .EXAMPLE 16 | ConvertFrom-WildcardPattern -Pattern AboutComp*, 17 | 18 | Returns the regular expression "^AboutComp". 19 | #> 20 | 21 | [CmdletBinding()] 22 | [OutputType([string])] 23 | param( 24 | [Parameter()] 25 | [SupportsWildcards()] 26 | [string[]] 27 | $Pattern 28 | ) 29 | 30 | $Parser = [PowerShell].Assembly.GetType('System.Management.Automation.WildcardPatternToRegexParser') 31 | 32 | $PatternBuilder = [StringBuilder]::new() 33 | foreach ($wildcardPattern in $Pattern) { 34 | $RegexPattern = $Parser::Parse( 35 | [WildcardPattern]::Get( 36 | $wildcardPattern, 37 | [WildcardOptions]::IgnoreCase 38 | ) 39 | ) 40 | 41 | if ($PatternBuilder.Length -gt 0) { 42 | $PatternBuilder.AppendFormat('|{0}', $RegexPattern.ToString()) > $null 43 | } 44 | else { 45 | $PatternBuilder.Append($RegexPattern) > $null 46 | } 47 | } 48 | 49 | $PatternBuilder.ToString() 50 | } 51 | -------------------------------------------------------------------------------- /PSKoans/Private/Get-KoanAst.ps1: -------------------------------------------------------------------------------- 1 | using namespace System.Management.Automation.Language 2 | 3 | function Get-KoanAst { 4 | <# 5 | .SYNOPSIS 6 | Get the AST for a Koan. 7 | 8 | .DESCRIPTION 9 | Parse the content of a Koan file into an AST. Ignores "using module" statements. 10 | 11 | .PARAMETER Path 12 | The path to a Koan file. 13 | 14 | #> 15 | 16 | [CmdletBinding()] 17 | [OutputType([System.Management.Automation.Language.ScriptBlockAst])] 18 | param ( 19 | [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 20 | [Alias('PSPath')] 21 | [string] 22 | $Path 23 | ) 24 | 25 | process { 26 | try { 27 | if (-not (Test-Path $Path)) { 28 | return 29 | } 30 | 31 | $tokens = $errors = $null 32 | 33 | # Remove the "using module" line. Avoids a slow call to Get-Module -ListAvailable from "using module". 34 | $content = Get-Content -Path $Path -Raw 35 | foreach ($match in [Regex]::Matches($content, 'using module \S+')) { 36 | $content = $content.Remove( 37 | $match.Index, 38 | $match.Length 39 | ).Insert( 40 | $match.Index, 41 | ' ' * $match.Length 42 | ) 43 | } 44 | 45 | [Parser]::ParseInput( 46 | $content, 47 | [Ref]$tokens, 48 | [Ref]$errors 49 | ) 50 | } 51 | catch { 52 | $PSCmdlet.ThrowTerminatingError($_) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /PSKoans/Private/Get-KoanAttribute.ps1: -------------------------------------------------------------------------------- 1 | using namespace System.Management.Automation.Language 2 | 3 | function Get-KoanAttribute { 4 | <# 5 | .SYNOPSIS 6 | Get the KoanAttribute from a file. 7 | .DESCRIPTION 8 | Modified koan file parser that avoids "using module" statements. Semantic checks for using module include 9 | invoking "Get-Module -ListAvailable" which adds a considerable delay when parsing individual files. 10 | #> 11 | 12 | [CmdletBinding()] 13 | [OutputType('PSKoans.KoanAttributeInfo')] 14 | param ( 15 | [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 16 | [Alias('PSPath')] 17 | [string] 18 | $Path 19 | ) 20 | 21 | begin { 22 | $defaultModule = [KoanAttribute]::new().Module 23 | } 24 | 25 | process { 26 | try { 27 | $ast = Get-KoanAst -Path $Path 28 | $attributeAst = $ast.Find( 29 | { 30 | param ( $node ) 31 | 32 | $node -is [AttributeAst] -and 33 | $node.TypeName.Name -in 'Koan', 'KoanAttribute' 34 | }, 35 | $false 36 | ) 37 | if (-not $attributeAst) { 38 | return 39 | } 40 | 41 | $namedArguments = $attributeAst.NamedArguments | Group-Object ArgumentName -AsHashTable -AsString 42 | 43 | [PSCustomObject]@{ 44 | Position = $namedArguments['Position'].Argument.SafeGetValue() 45 | Module = if ($namedArguments.Contains('Module')) { 46 | $namedArguments['Module'].Argument.SafeGetValue() 47 | } 48 | else { 49 | $defaultModule 50 | } 51 | PSTypeName = 'PSKoans.KoanAttributeInfo' 52 | } 53 | } catch { 54 | $ErrorDetails = @{ 55 | Exception = $_.Exception 56 | ErrorId = 'PSKoans.KoanAttributeParserFailed' 57 | ErrorCategory = 'OperationStopped' 58 | TargetObject = $Path 59 | } 60 | Write-Error -ErrorRecord (New-PSKoanErrorRecord @ErrorDetails) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PSKoans/Private/Get-KoanIt.ps1: -------------------------------------------------------------------------------- 1 | using namespace System.Management.Automation.Language 2 | 3 | function Get-KoanIt { 4 | <# 5 | .SYNOPSIS 6 | Get the It blocks from AST. 7 | 8 | .DESCRIPTION 9 | Returns all CommandAst nodes for the It command from a specific Koan file. 10 | 11 | .PARAMETER Path 12 | The path to the file to search. 13 | 14 | .EXAMPLE 15 | Get-KoanIt -Path C:\userKoanDir\Foundations\AboutArrays.Koans.ps1 16 | 17 | Returns all It blocks from the AboutArrays topic. 18 | #> 19 | 20 | [CmdletBinding()] 21 | param( 22 | [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 23 | [Alias('FullName')] 24 | [string] 25 | $Path 26 | ) 27 | 28 | process { 29 | $ast = Get-KoanAst -Path $Path 30 | $koanList = $ast.FindAll( 31 | { 32 | param ( $node ) 33 | 34 | $node -is [CommandAst] -and $node.GetCommandName() -eq 'It' 35 | }, 36 | $true 37 | ) 38 | 39 | foreach ($koan in $koanList) { 40 | $contextName = '' 41 | $parentAst = $koan 42 | do { 43 | $parentAst = $parentAst.Parent 44 | if ($parentAst -is [CommandAst] -and $parentAst.GetCommandName() -eq 'Context') { 45 | $contextName = $parentAst.CommandElements[1].Value 46 | } 47 | } while ($parentAst -and -not $contextName) 48 | 49 | [PSCustomObject]@{ 50 | ID = '{0}/{1}' -f $contextName, $koan.CommandElements[1].Value 51 | Name = $koan.CommandElements[1].Value 52 | Context = $contextName 53 | Ast = $koan 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /PSKoans/Private/Invoke-Koan.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Koan { 2 | <# 3 | .SYNOPSIS 4 | Safely invokes Pester on a koan file in a fresh scope where tests can be executed out of harm's way. 5 | 6 | .DESCRIPTION 7 | Creates a new instance of PowerShell to execute the Pester tests in. Requires a valid parameter-splat hashtable 8 | for Invoke-Pester. 9 | 10 | .PARAMETER ParameterSplat 11 | Defines the hashtable that will be splatted into Invoke-Pester in the new PowerShell instance. 12 | 13 | .EXAMPLE 14 | Invoke-Koan @{ Script = '.\AboutArrays.Koans.ps1'; PassThru = $true; Show = 'None' } 15 | 16 | Triggers Pester to assess the AboutArrays file in the current directory and pass back the complete tests object, 17 | hiding the standard test results display. 18 | #> 19 | 20 | [CmdletBinding()] 21 | [OutputType([PSObject])] 22 | param( 23 | [Parameter(Position = 0, Mandatory)] 24 | [Alias('Params')] 25 | [hashtable] 26 | $ParameterSplat 27 | ) 28 | begin { 29 | if (-not $script:KoanRunspace) { 30 | $script:KoanRunspace = New-KoanRunspace 31 | } 32 | } 33 | end { 34 | try { 35 | $Requirements = [System.Management.Automation.Language.Parser]::ParseFile( 36 | $ParameterSplat.Script, 37 | [ref]$null, 38 | [ref]$null 39 | ).Ast.ScriptRequirements 40 | 41 | $Script = { 42 | param( $Params, $RequiredModules, $PSModulePath ) 43 | 44 | $oldModulePath = $env:PSModulePath 45 | try { 46 | $env:PSModulePath = @( 47 | $PSKoansPath | Split-Path -Parent 48 | $PSModulePath -split [System.IO.Path]::PathSeparator 49 | $env:PSModulePath -split [System.IO.Path]::PathSeparator 50 | ) -join [System.IO.Path]::PathSeparator 51 | 52 | foreach ($module in $RequiredModules) { 53 | Import-Module $module 54 | } 55 | 56 | . ([scriptblock]::Create('using module PSKoans')) 57 | Invoke-Pester @Params 58 | } 59 | finally { 60 | $env:PSModulePath = $oldModulePath 61 | } 62 | } 63 | 64 | $ps = [powershell]::Create($script:KoanRunspace) 65 | $ps.AddScript($Script, <# useLocalScope: #> $true) > $null 66 | 67 | $ps.AddParameter('Params', $ParameterSplat) > $null 68 | $ps.AddParameter('PSModulePath', $env:PSModulePath) > $null 69 | 70 | if ($Requirements.RequiredModules) { 71 | $ps.AddParameter('RequiredModules', $Requirements.RequiredModules) 72 | } 73 | 74 | $Status = $ps.BeginInvoke() 75 | 76 | do { Start-Sleep -Milliseconds 100 } until ($Status.IsCompleted) 77 | 78 | $Result = $ps.EndInvoke($Status) 79 | 80 | if ($ps.HadErrors) { 81 | # These will be errors outside the test itself; better propagate them upwards. 82 | foreach ($errorItem in $ps.Streams.Error) { 83 | $PSCmdlet.WriteError($errorItem) 84 | } 85 | } 86 | 87 | $Result 88 | } 89 | finally { 90 | $ps.Dispose() 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /PSKoans/Private/Measure-Koan.ps1: -------------------------------------------------------------------------------- 1 | function Measure-Koan { 2 | <# 3 | .SYNOPSIS 4 | Counts the number of koans in the provided ExternalScriptInfo objects. 5 | .DESCRIPTION 6 | Traverses the AST of the script blocks taken from the Get-Command output of the koan 7 | files to find all of the 'It' blocks in order to count the total number of Pester 8 | tests present in the file. 9 | 10 | When provided with a piped list of KoanInfo objects, sums the entire 11 | collection's 'It' blocks and returns a single integer sum. 12 | .PARAMETER KoanInfo 13 | Takes an array of KoanInfo objects (as provided from Get-PSKoan). 14 | .EXAMPLE 15 | Get-Command .\KoanDirectory\*\*.ps1 | Measure-Koan 16 | 17 | 422 18 | .NOTES 19 | Author: Joel Sallow 20 | Module: PSKoans 21 | .LINK 22 | https://github.com/vexx32/PSKoans 23 | #> 24 | [CmdletBinding()] 25 | [OutputType([int])] 26 | param( 27 | [Parameter(Position = 0, Mandatory, ValueFromPipeline)] 28 | [PSTypeName('PSKoans.KoanInfo')] 29 | [psobject[]] 30 | $KoanInfo 31 | ) 32 | begin { 33 | $KoanCount = 0 34 | $oldModulePath = $env:PSModulePath 35 | 36 | $env:PSModulePath = @( 37 | $MyInvocation.MyCommand.Module.ModuleBase 38 | $env:PSModulePath -split [System.IO.Path]::PathSeparator 39 | ) -join [System.IO.Path]::PathSeparator 40 | } 41 | process { 42 | Write-Verbose "Discovering koans in [$($KoanInfo.Name -join '], [')]" 43 | 44 | $Result = & (Get-Module Pester) { 45 | [CmdletBinding()] 46 | param( 47 | $Path, 48 | $ExcludePath, 49 | $SessionState 50 | ) 51 | 52 | $_Pester_State_Backup = $state.PSObject.Copy() 53 | $state.Stack = [System.Collections.Stack]@() 54 | try { 55 | Reset-TestSuiteState 56 | 57 | # to avoid Describe thinking that we run in interactive mode 58 | $invokedViaInvokePester = $true 59 | 60 | $fileList = Find-File -Path $Path -ExcludePath $ExcludePath -Extension '.Koans.ps1' 61 | $containers = foreach ($file in $fileList) { 62 | New-BlockContainerObject -File (Get-Item $file) 63 | } 64 | 65 | Find-Test -BlockContainer $containers -SessionState $SessionState 66 | } 67 | finally { 68 | $state = $_Pester_State_Backup 69 | Remove-Variable -Name _Pester_State_Backup 70 | } 71 | } -Path $KoanInfo.Path -SessionState $PSCmdlet.SessionState 72 | 73 | $KoanCount += Measure-KoanTestBlock $Result 74 | } 75 | end { 76 | Write-Verbose "Total Koans: $KoanCount" 77 | $KoanCount 78 | 79 | $env:PSModulePath = $oldModulePath 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /PSKoans/Private/Measure-KoanBlockTest.ps1: -------------------------------------------------------------------------------- 1 | function Measure-KoanTestBlock { 2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Mandatory)] 5 | [psobject] 6 | $Block 7 | ) 8 | 9 | $Label = if ($Block.Name) { 10 | "Block $($Block.Name)" 11 | } 12 | else { 13 | "File $($Block.BlockContainer.Item)" 14 | } 15 | 16 | $Count = $Block.Tests.Count 17 | foreach ($testBlock in $Block.Blocks) { 18 | $Count += Measure-KoanTestBlock $testBlock 19 | } 20 | 21 | $Count 22 | Write-Information "$Label Tests: $Count" 23 | } 24 | -------------------------------------------------------------------------------- /PSKoans/Private/New-KoanRunspace.ps1: -------------------------------------------------------------------------------- 1 | function New-KoanRunspace { 2 | <# 3 | .SYNOPSIS 4 | Creates a new runspace, with PSKoans and Pester already imported, and PSModulePath already set. 5 | 6 | .DESCRIPTION 7 | Creates a new runspace for reuse with Invoke-Koan. The new runspace will be pre-populated with a fresh copy of 8 | Pester and PSKoans imported, and with a copy of the current session state's PSModulePath, which will have the 9 | current PSKoans parent folder in it, such that `using module` statements will find the current PSKoans module 10 | first, avoiding issues with debug environments which may find older PSKoans modules installed instead of using the 11 | current debug version. 12 | 13 | .EXAMPLE 14 | $script:KoanRunspace = New-KoanRunspace 15 | 16 | .NOTES 17 | Run scripts in a new scope to avoid scope bleed wherever possible, with the `$ps.AddScript($script, $true) overload. 18 | #> 19 | [CmdletBinding()] 20 | param() 21 | 22 | $runspace = [runspacefactory]::CreateRunspace() 23 | $runspace.Open() 24 | $runspace.Name = 'PSKoans.KoanRunspace' 25 | $ps = [powershell]::Create($runspace) 26 | 27 | try { 28 | $script = { 29 | param( $PSKoansPath ) 30 | 31 | Get-Module $PSKoansPath -ListAvailable | Import-Module 32 | } 33 | 34 | $ps.AddScript($script) > $null 35 | $ps.AddParameter('PSKoansPath', $MyInvocation.MyCommand.Module.ModuleBase) > $null 36 | $ps.Invoke() > $null 37 | 38 | $runspace 39 | } 40 | finally { 41 | $ps.Dispose() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PSKoans/Private/New-PSKoanErrorRecord.ps1: -------------------------------------------------------------------------------- 1 | function New-PSKoanErrorRecord { 2 | <# 3 | .SYNOPSIS 4 | Constructs an error record for other functions to throw. 5 | 6 | .DESCRIPTION 7 | Creates an error record using the given parameters and outputs it as an object. This error record can later be 8 | thrown by other functions. 9 | 10 | .PARAMETER ExceptionType 11 | The type name or [type] object representing a specific [Exception]. Only types that inherit [Exception] in some 12 | way can be used. 13 | 14 | .PARAMETER ExceptionMessage 15 | The message to store in the Exception object of the ErrorRecord. 16 | 17 | .PARAMETER Exception 18 | A complete [Exception] object already constructed can be passed directly to this parameter. 19 | 20 | .PARAMETER ErrorId 21 | A unique string identifying this ErrorRecord. 22 | 23 | .PARAMETER ErrorCategory 24 | The standard category name or value for this ErrorRecord. 25 | 26 | .PARAMETER TargetObject 27 | The object or command which raised the exception, or caused the exception to be raised. 28 | 29 | .EXAMPLE 30 | $ErrorDetails = @{ 31 | ExceptionType = 'System.IO.FileNotFoundException' 32 | ExceptionMessage = 'Could not find any koans that match the specified Topic(s)' 33 | ErrorId = 'PSKoans.NoMatchingKoansFound' 34 | ErrorCategory = 'ObjectNotFound' 35 | TargetObject = $Topic -join ',' 36 | } 37 | $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) 38 | 39 | Constructs an error record out of disparate parts and throws it to terminate the current command. 40 | #> 41 | 42 | [CmdletBinding(DefaultParameterSetName = 'Default')] 43 | [OutputType([System.Management.Automation.ErrorRecord])] 44 | param( 45 | [Parameter(ParameterSetName = 'Default')] 46 | [Alias('Type')] 47 | [ValidateScript( 48 | { 49 | if (-not [Exception].IsAssignableFrom( ($_ -as [Type]) )) { 50 | throw 'The entered type name is not a valid exception type' 51 | } 52 | else { 53 | $true 54 | } 55 | } 56 | )] 57 | [Type] 58 | $ExceptionType = [Exception], 59 | 60 | [Parameter(ParameterSetName = 'Default')] 61 | [Alias('Message')] 62 | [string] 63 | $ExceptionMessage, 64 | 65 | [Parameter(Mandatory, ParameterSetName = 'ExceptionObject')] 66 | [Exception] 67 | $Exception, 68 | 69 | [Parameter()] 70 | [Alias('Id')] 71 | [string] 72 | $ErrorId = 'PSKoans.GenericError', 73 | 74 | [Parameter()] 75 | [Alias('Category')] 76 | [System.Management.Automation.ErrorCategory] 77 | $ErrorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified, 78 | 79 | [Parameter()] 80 | [PSObject] 81 | $TargetObject 82 | ) 83 | process { 84 | if ($PSCmdlet.ParameterSetName -eq 'Default') { 85 | $Exception = $ExceptionType::new( $ExceptionMessage ) 86 | } 87 | 88 | if ($ErrorId -notmatch '^PSKoans\.') { 89 | $ErrorId = "PSKoans.$($ErrorId)" 90 | } 91 | 92 | [System.Management.Automation.ErrorRecord]::new( 93 | $Exception, 94 | $ErrorId, 95 | $ErrorCategory, 96 | $TargetObject 97 | ) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PSKoans/Private/Update-PSKoanFile.ps1: -------------------------------------------------------------------------------- 1 | function Update-PSKoanFile { 2 | <# 3 | .SYNOPSIS 4 | Update the koans for a specific topic. 5 | 6 | .DESCRIPTION 7 | Update a user Koan topic with new koans. 8 | 9 | Existing answers are preserved. Expired koans will be removed and new koans will be added. 10 | 11 | .PARAMETER Topic 12 | Updates the specified topic from the module. 13 | 14 | .EXAMPLE 15 | Update-PSKoanFile -Topic AboutArrays 16 | 17 | Updates the AboutArrays topic with any new koans from the module. 18 | #> 19 | 20 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] 21 | [OutputType([void])] 22 | param( 23 | [Parameter()] 24 | [Alias('Koan', 'File')] 25 | [SupportsWildcards()] 26 | [string[]] 27 | $Topic, 28 | 29 | [Parameter()] 30 | [SupportsWildcards()] 31 | [string[]] 32 | $Module, 33 | 34 | [Parameter()] 35 | [SupportsWildcards()] 36 | [string[]] 37 | $IncludeModule 38 | ) 39 | 40 | $PSBoundParameters.Remove('Confirm') > $null 41 | $PSBoundParameters.Remove('WhatIf') > $null 42 | Get-PSKoan @PSBoundParameters | ForEach-Object { 43 | $moduleKoans = Get-KoanIt -Path $_.Path | 44 | ForEach-Object { 45 | [PSCustomObject]@{ 46 | ID = $_.ID 47 | Name = $_.Name 48 | Ast = $_.Ast 49 | } 50 | } | 51 | Group-Object -Property ID -AsHashTable -AsString 52 | 53 | if (-not $moduleKoans) { 54 | # Handles topics which do not have It blocks. 55 | return 56 | } 57 | 58 | $path = Get-PSKoanLocation | Join-Path -ChildPath $_.RelativePath 59 | 60 | if (Test-Path -Path $path) { 61 | $userKoans = Get-KoanIt -Path $path 62 | $userKoansHash = $userKoans | Group-Object ID -AsHashTable -AsString 63 | 64 | if ($moduleKoans.Keys.Where{ -not ($userKoansHash -and $userKoansHash.Contains($_)) }) { 65 | $content = Get-Content -Path $_.Path -Raw 66 | 67 | $userKoans | 68 | Where-Object { $moduleKoans.Contains($_.ID) } | 69 | Select-Object -Property @( 70 | 'ID' 71 | 'Name' 72 | 'Ast' 73 | @{ Name = 'SourceAst'; Expression = { $moduleKoans[$_.ID].Ast } } 74 | ) | 75 | Sort-Object { $_.SourceAst.Extent.StartLineNumber } -Descending | 76 | ForEach-Object { 77 | # Replace the content of the koan with the users content. 78 | $content = $content.Remove( 79 | $_.SourceAst.Extent.StartOffset, 80 | ($_.SourceAst.Extent.EndOffset - $_.SourceAst.Extent.StartOffset) 81 | ).Insert( 82 | $_.SourceAst.Extent.StartOffset, 83 | $_.Ast.Extent.Text 84 | ) 85 | } 86 | 87 | Set-Content -Path $path -Value $content.TrimEnd() -NoNewline 88 | } 89 | } 90 | else { 91 | Write-Warning ('Unexpected error, the koan topic {0} does not exist in the user store' -f $_.Name) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /PSKoans/Private/Write-ConsoleLine.ps1: -------------------------------------------------------------------------------- 1 | function Write-ConsoleLine { 2 | <# 3 | .SYNOPSIS 4 | Writes text to the console. 5 | .DESCRIPTION 6 | Cuts text to console-appropriate widths for viewing, where possible. 7 | .EXAMPLE 8 | Write-ConsoleLine $String 9 | 10 | Writes the contents of $String to the console, inserting new line characters 11 | where appropriate. 12 | .INPUTS 13 | Write-ConsoleLine accepts pipeline input for the $InputString parameter. 14 | .OUTPUTS 15 | Nothing. All output is sent only to the host / information stream. 16 | #> 17 | [CmdletBinding()] 18 | [OutputType([void])] 19 | param( 20 | [Parameter(Position = 0, Mandatory, ValueFromPipeline)] 21 | [ValidateNotNullOrEmpty()] 22 | [string[]] 23 | $InputString, 24 | 25 | [Parameter()] 26 | [switch] 27 | $Title 28 | ) 29 | begin { 30 | Write-Host "" 31 | 32 | if ($Title) { 33 | Write-Host "Those who came before offer you a fragment of wisdom." -ForegroundColor Green 34 | Write-Host "" 35 | 36 | $Prefix = " " * 5 37 | $Color = @{ ForegroundColor = [ConsoleColor]::Yellow } 38 | } 39 | else { 40 | $Prefix = " " * 3 41 | $Color = @{ ForegroundColor = [ConsoleColor]::Gray } 42 | } 43 | 44 | $Width = $host.UI.RawUI.WindowSize.Width - ($Prefix.Length + 2) 45 | } 46 | process { 47 | # Ugly mode since width either not detectable or too small to bother 48 | if ($Width -lt 20) { 49 | Write-Host $InputString 50 | } 51 | else { 52 | $RemainingText = $InputString.TrimEnd() 53 | 54 | while ($RemainingText.Length -gt $Width) { 55 | $CompleteLine = $RemainingText.Substring(0, $Width) 56 | $TailFragment = ($CompleteLine -split "[- ]")[-1].Length 57 | $BestFitLine = $CompleteLine.Substring(0, $CompleteLine.Length - $TailFragment).TrimEnd() 58 | 59 | Write-Host ($Prefix + $BestFitLine) @Color 60 | 61 | $RemainingText = $RemainingText.Substring($CompleteLine.Length - $TailFragment) 62 | } 63 | Write-Host ($Prefix + $RemainingText) @Color 64 | } 65 | } 66 | end { 67 | if (-not $Title) { Write-Host "" } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PSKoans/Public/Get-Blank.ps1: -------------------------------------------------------------------------------- 1 | function Get-Blank { 2 | [CmdletBinding(HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Get-Blank.md')] 3 | [OutputType('Blank')] 4 | [Alias('__', '____', 'FILL_ME_IN')] 5 | param( 6 | [Parameter(ValueFromPipeline, DontShow)] 7 | [object] 8 | ${|PipeInput}, 9 | 10 | [Parameter(ValueFromRemainingArguments, DontShow)] 11 | [object[]] 12 | ${|ParameterInput} 13 | ) 14 | 15 | Write-Verbose "I AIN'T DOIN' NOTHIN'!!!" 16 | 17 | return [Blank]::New() 18 | } 19 | -------------------------------------------------------------------------------- /PSKoans/Public/Get-PSKoan.ps1: -------------------------------------------------------------------------------- 1 | function Get-PSKoan { 2 | [CmdletBinding(DefaultParameterSetName = 'IncludeModule', 3 | HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoan.md')] 4 | [OutputType('PSKoans.KoanInfo')] 5 | param( 6 | [Parameter()] 7 | [SupportsWildcards()] 8 | [string[]] 9 | $Topic, 10 | 11 | [Parameter(ParameterSetName = 'ModuleOnly')] 12 | [SupportsWildcards()] 13 | [string[]] 14 | $Module, 15 | 16 | [Parameter(ParameterSetName = 'IncludeModule')] 17 | [SupportsWildcards()] 18 | [string[]] 19 | $IncludeModule, 20 | 21 | [ValidateSet('User', 'Module')] 22 | [string] 23 | $Scope = 'Module', 24 | 25 | [Parameter()] 26 | [switch] 27 | $SkipAttributeParsing, 28 | 29 | [Parameter(Mandatory, ParameterSetName = 'ListModules')] 30 | [switch] 31 | $ListModules 32 | ) 33 | 34 | $ParentPath = switch ($Scope) { 35 | 'User' { 36 | $KoanLocation = Get-PSKoanLocation 37 | Write-Verbose "Checking existence of koans folder" 38 | if (-not (Test-Path $KoanLocation)) { 39 | Write-Verbose "Koans folder does not exist. Initiating full reset..." 40 | Update-PSKoan -Confirm:$false 41 | } 42 | 43 | $KoanLocation 44 | } 45 | 'Module' { Join-Path -Path $script:ModuleRoot -ChildPath 'Koans' } 46 | } 47 | 48 | if ($PSCmdlet.ParameterSetName -eq 'ListModules') { 49 | $modulesPath = Join-Path -Path $ParentPath -ChildPath 'Modules' 50 | 51 | if (Test-Path $modulesPath) { 52 | $modulesPath | 53 | Get-ChildItem -Directory | 54 | Select-Object -ExpandProperty Name 55 | } 56 | 57 | return 58 | } 59 | 60 | $KoanDirectories = switch ($PSCmdlet.ParameterSetName) { 61 | 'IncludeModule' { 62 | $Module = $IncludeModule 63 | Get-ChildItem $ParentPath -Exclude Modules -Directory 64 | } 65 | { $Module } { 66 | $ModuleRegex = ConvertFrom-WildcardPattern -Pattern $Module 67 | 68 | $modulesPath = Join-Path -Path $ParentPath -ChildPath 'Modules' 69 | if (Test-Path $modulesPath) { 70 | Get-ChildItem $modulesPath -Directory | 71 | Where-Object { $_.Name -match $ModuleRegex } 72 | } 73 | } 74 | } 75 | 76 | $TopicRegex = ConvertFrom-WildcardPattern -Pattern $Topic 77 | $ParentPathPattern = [regex]::Escape($parentPath) 78 | # Declared to avoid scope problems when attribute parsing is not required. 79 | $KoanAttribute = $null 80 | try { 81 | $KoanDirectories | 82 | Get-ChildItem -Recurse -Filter *.Koans.ps1 | 83 | Where-Object { -not $Topic -or $_.BaseName -replace '\.Koans$' -match $TopicRegex } | 84 | Assert-UnblockedFile -PassThru | 85 | ForEach-Object { 86 | if (-not $SkipAttributeParsing) { 87 | $KoanAttribute = Get-KoanAttribute -Path $_.FullName 88 | 89 | if (-not $KoanAttribute) { 90 | return 91 | } 92 | } 93 | 94 | [PSCustomObject]@{ 95 | Topic = $_.BaseName -replace '\.koans$' 96 | Module = $KoanAttribute.Module 97 | Position = $koanAttribute.Position 98 | Path = $_.FullName 99 | RelativePath = $_.Fullname -replace $ParentPathPattern -replace '^\\' 100 | PSTypeName = 'PSKoans.KoanInfo' 101 | } 102 | } | 103 | Sort-Object { $_.Module -ne '_powershell' }, Module, Position, Topic 104 | } 105 | catch { 106 | $pscmdlet.ThrowTerminatingError($_) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /PSKoans/Public/Get-PSKoanLocation.ps1: -------------------------------------------------------------------------------- 1 | function Get-PSKoanLocation { 2 | [CmdletBinding(HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanLocation.md')] 3 | [OutputType([string])] 4 | param() 5 | process { 6 | $Location = Get-PSKoanSetting -Name KoanLocation 7 | if ($Location) { 8 | $Location 9 | } 10 | else { 11 | $ErrorDetails = @{ 12 | Exception = [System.IO.DirectoryNotFoundException]::new( 13 | 'PSKoans folder location has not been defined' 14 | ) 15 | ErrorId = 'PSKoans.LibraryFolderNotDefined' 16 | ErrorCategory = 'NotSpecified' 17 | TargetObject = $MyInvocation.MyCommand.Name 18 | } 19 | $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PSKoans/Public/Get-PSKoanSetting.ps1: -------------------------------------------------------------------------------- 1 | function Get-PSKoanSetting { 2 | [CmdletBinding(HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanSetting.md')] 3 | [OutputType([string], [PSCustomObject])] 4 | param( 5 | [Parameter()] 6 | [string] 7 | $Name 8 | ) 9 | 10 | $Configuration = if (-not (Test-Path $script:ConfigPath)) { 11 | # No settings file present, create file with default settings 12 | Set-PSKoanSetting -Settings $script:DefaultSettings -Confirm:$false 13 | [PSCustomObject]$script:DefaultSettings 14 | } 15 | else { 16 | Get-Content -Path $script:ConfigPath | ConvertFrom-Json 17 | } 18 | 19 | if ($Name) { 20 | $Configuration.$Name 21 | } 22 | else { 23 | $Configuration 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PSKoans/Public/Move-PSKoanLibrary.ps1: -------------------------------------------------------------------------------- 1 | function Move-PSKoanLibrary { 2 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', 3 | HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Move-PSKoanLibrary.md')] 4 | [OutputType([void])] 5 | param( 6 | [Parameter(Mandatory, Position = 0, ValueFromPipeline)] 7 | [Alias('PSPath', 'Folder', 'Destination', 'TargetPath')] 8 | [string] 9 | $Path 10 | ) 11 | process { 12 | if ($PSCmdlet.ShouldProcess($Path, 'Move existing koan files here')) { 13 | $OriginalPath = Get-PSKoanLocation 14 | 15 | Write-Verbose "Moving library files from '$OriginalPath' to '$Path'" 16 | Move-Item -Path $OriginalPath -Destination $Path -ErrorAction Stop -PassThru 17 | 18 | if ($?) { 19 | Set-PSKoanLocation -Path $Path 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PSKoans/Public/Register-Advice.ps1: -------------------------------------------------------------------------------- 1 | function Register-Advice { 2 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low', 3 | HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Register-Advice.md')] 4 | [OutputType([void])] 5 | param( 6 | [Parameter(Position = 0)] 7 | [ValidateSet('AllUsersAllHosts', 'AllUsersCurrentHost', 'CurrentUserAllHosts', 'CurrentUserCurrentHost')] 8 | [string] 9 | $TargetProfile = 'CurrentUserCurrentHost' 10 | ) 11 | 12 | $ProfilePath = $Profile.$TargetProfile 13 | 14 | if ($PSCmdlet.ShouldProcess("$TargetProfile PowerShell profile", 'Register Show-Advice')) { 15 | $ProfileFolder = Split-Path -Path $ProfilePath 16 | 17 | if (-not (Test-Path $ProfileFolder)) { 18 | New-Item $ProfileFolder -ItemType Directory -Force > $null 19 | } 20 | 21 | if (-not (Test-Path $ProfilePath)) { 22 | Set-Content -Path $ProfilePath -Value 'Show-Advice' 23 | } 24 | elseif (-not (Select-String -Path $ProfilePath -Pattern '(Show|Get)-Advice' -Quiet)) { 25 | 'Show-Advice' | Add-Content $ProfilePath 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PSKoans/Public/Set-PSKoanLocation.ps1: -------------------------------------------------------------------------------- 1 | function Set-PSKoanLocation { 2 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', 3 | HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanLocation.md')] 4 | [OutputType([void])] 5 | param( 6 | [Parameter(Mandatory, Position = 0)] 7 | [Alias('PSPath', 'Folder')] 8 | [string] 9 | $Path, 10 | 11 | [Parameter()] 12 | [switch] 13 | $PassThru 14 | ) 15 | begin { 16 | $resolvedPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) 17 | 18 | if ($resolvedPath.Count -gt 1 -or [WildcardPattern]::ContainsWildcardCharacters($resolvedPath)) { 19 | $ErrorDetails = @{ 20 | ExceptionType = [System.Management.Automation.PSArgumentException] 21 | ExceptionMessage = 'Wildcarded paths are not supported.' 22 | ErrorId = 'InvalidPath' 23 | ErrorCategory = 'InvalidArgument' 24 | TargetObject = $Path 25 | } 26 | $PSCmdlet.ThrowTerminatingError((New-PSKoanErrorRecord @ErrorDetails)) 27 | } 28 | 29 | if (Test-Path $resolvedPath -PathType Leaf) { 30 | $ErrorDetails = @{ 31 | ExceptionType = [System.Management.Automation.PSArgumentException] 32 | ExceptionMessage = 'You cannot use a file path as the location for your PSKoans library.' 33 | ErrorId = 'InvalidPathType' 34 | ErrorCategory = 'InvalidArgument' 35 | TargetObject = $Path 36 | } 37 | $PSCmdlet.ThrowTerminatingError((New-PSKoanErrorRecord @ErrorDetails)) 38 | } 39 | } 40 | process { 41 | if ($PSCmdlet.ShouldProcess("Set PSKoans folder location to '$resolvedPath'")) { 42 | Set-PSKoanSetting -Name KoanLocation -Value $resolvedPath 43 | } 44 | else { 45 | Write-Warning "PSKoans folder location has not been changed." 46 | } 47 | 48 | if ($PassThru) { 49 | $resolvedPath 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PSKoans/Public/Set-PSKoanSetting.ps1: -------------------------------------------------------------------------------- 1 | function Set-PSKoanSetting { 2 | [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Single', 3 | HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanSetting.md')] 4 | [OutputType([void])] 5 | param( 6 | [Parameter(Position = 0, Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Single')] 7 | [string] 8 | $Name, 9 | 10 | [Parameter(Position = 1, Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'Single')] 11 | [object] 12 | $Value, 13 | 14 | [Parameter(Position = 0, Mandatory, ValueFromPipeline, ParameterSetName = 'Multiple')] 15 | [hashtable] 16 | $Settings, 17 | 18 | [Parameter(ParameterSetName = 'Reset')] 19 | [switch] 20 | $Reset 21 | ) 22 | 23 | if ($PSCmdlet.ShouldProcess($script:ConfigPath, "Update configuration file")) { 24 | $CurrentSettings = if (Test-Path $script:ConfigPath) { 25 | Get-Content -Path $script:ConfigPath | ConvertFrom-Json 26 | } 27 | else { 28 | $ConfigRoot = $script:ConfigPath | Split-Path -Parent 29 | 30 | if (-not (Test-Path $ConfigRoot)) { 31 | New-Item -ItemType Directory -Path $ConfigRoot > $null 32 | } 33 | 34 | [PSCustomObject]$script:DefaultSettings 35 | } 36 | 37 | $NewSettings = switch ($PSCmdlet.ParameterSetName) { 38 | 'Single' { 39 | $CurrentSettings | 40 | Select-Object -Property *, @{ Name = $Name; Expression = { $Value } } -ExcludeProperty $Name 41 | } 42 | 'Multiple' { 43 | $Properties = @( 44 | '*' 45 | foreach ($key in $Settings.Keys) { 46 | @{ 47 | Name = $key 48 | Expression = { $Settings[$key] }.GetNewClosure() 49 | } 50 | } 51 | ) 52 | $CurrentSettings | 53 | Select-Object -Property $Properties -ExcludeProperty $Settings.Keys.ForEach{ $_ } 54 | } 55 | 'Reset' { 56 | $CurrentSettings 57 | } 58 | } 59 | 60 | $NewSettings | 61 | ConvertTo-Json | 62 | Set-Content -Path $script:ConfigPath 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /PSKoans/Public/Show-Advice.ps1: -------------------------------------------------------------------------------- 1 | function Show-Advice { 2 | [CmdletBinding(HelpUri = 'https://github.com/vexx32/PSKoans/tree/main/docs/Show-Advice.md')] 3 | [Alias('Get-Advice')] 4 | [OutputType([void])] 5 | param( 6 | [Parameter(Position = 0)] 7 | [string] 8 | $Name = "*" 9 | ) 10 | begin { 11 | $AdviceFolder = $script:ModuleRoot | Join-Path -ChildPath 'Data/Advice' 12 | } 13 | process { 14 | $AdviceObject = Get-ChildItem -Path $AdviceFolder -Recurse -File -Filter "$Name.Advice.json" | 15 | Get-Random | 16 | Get-Content | 17 | ConvertFrom-Json 18 | 19 | # Checking for errors 20 | if (-not $AdviceObject) { 21 | $ErrorDetails = @{ 22 | ExceptionType = 'System.IO.FileNotFoundException' 23 | ExceptionMessage = 'Could not find any Advice files matching the specified Name: {0}.' -f $Name 24 | ErrorId = 'PSKoans.NoMatchingAdviceFound' 25 | ErrorCategory = 'ObjectNotFound' 26 | TargetObject = $Name 27 | } 28 | $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) 29 | } 30 | elseif (-not ($AdviceObject.Title -and $AdviceObject.Content)) { 31 | $ErrorDetails = @{ 32 | ExceptionType = 'System.IO.FileLoadException' 33 | ExceptionMessage = 'Could not find Title and/or Content elements for Advice file: {0}' -f $Name 34 | ErrorId = 'PSKoans.IncorrectAdviceData' 35 | ErrorCategory = 'InvalidData' 36 | TargetObject = $Name 37 | } 38 | $PSCmdlet.ThrowTerminatingError( (New-PSKoanErrorRecord @ErrorDetails) ) 39 | } 40 | 41 | $AdviceObject.Title | Write-ConsoleLine -Title 42 | $AdviceObject.Content | Write-ConsoleLine 43 | } 44 | end { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Start-DebugSession.ps1: -------------------------------------------------------------------------------- 1 | $env:PSModulePath = $( 2 | @( 3 | $env:PSModulePath -split [System.IO.Path]::PathSeparator 4 | $PSScriptRoot 5 | ) | Select-Object -Unique 6 | ) -join [System.IO.Path]::PathSeparator 7 | 8 | & $PSScriptRoot/PSKoans.ezformat.ps1 9 | Import-Module $PSScriptRoot/PSKoans 10 | 11 | ### Enter code to test below 12 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Assert-UnblockedFile.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | #region Discovery 4 | $SkipTests = $PSVersionTable.PSEdition -ne 'Desktop' -or $PSVersionTable.Platform -ne 'Win32NT' 5 | #endregion Discovery 6 | 7 | Describe 'Assert-UnblockedFile' -Skip:$SkipTests { 8 | 9 | BeforeAll { 10 | $defaultParams = @{ 11 | FileInfo = [System.IO.FileInfo](Join-Path -Path $TestDrive -ChildPath 'AboutSomething.Koans.ps1') 12 | PassThru = $true 13 | } 14 | } 15 | 16 | BeforeEach { 17 | Set-Content -Path $defaultParams.FileInfo.FullName -Value @' 18 | using module PSKoans 19 | [Koan(Position = 1)] 20 | param() 21 | 22 | Describe 'About something' { 23 | 24 | It 'Has examples' { 25 | $true | Should -BeTrue 26 | } 27 | } 28 | '@ 29 | } 30 | 31 | AfterEach { 32 | Remove-Item -Path $defaultParams.FileInfo.FullName 33 | } 34 | 35 | Context 'File With External Zone Identifier' { 36 | 37 | BeforeEach { 38 | Set-Content -Path $defaultParams.FileInfo.FullName -Stream Zone.Identifier -Value @' 39 | [ZoneTransfer] 40 | ZoneId=3 41 | ReferrerUrl=C:\Downloads\File.zip 42 | '@ 43 | } 44 | 45 | It 'should throw a terminating error if the file is blocked' { 46 | { 47 | InModuleScope 'PSKoans' -Parameters @{ Params = $defaultParams } { 48 | param($Params) 49 | Assert-UnblockedFile @Params 50 | } 51 | } | Should -Throw -ErrorId 'PSKoans.KoanFileIsBlocked' 52 | } 53 | } 54 | 55 | Context 'File Without Zone Identifier' { 56 | 57 | It 'returns the original object with -PassThru if the file is not blocked' { 58 | InModuleScope 'PSKoans' -Parameters @{ Params = $defaultParams } { 59 | param($Params) 60 | Assert-UnblockedFile @Params 61 | } | Should -BeOfType [System.IO.FileInfo] 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/Functions/Private/ControlTests/Invoke-Koan.Control_Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Deliberately failing tests' { 2 | It 'simply fails' { 3 | Should -Fail -Because 'it is written that way' 4 | } 5 | 6 | It 'fails with a less clear error' { 7 | throw [NotImplementedException]::new('Test is invalid.') 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/Functions/Private/ConvertFrom-WildcardPattern.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'ConvertFrom-WildcardPattern' { 4 | 5 | It 'adds start and end of string anchors to an explicit value' { 6 | InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern 'AboutArrays' } | Should -Be '^AboutArrays$' 7 | } 8 | 9 | It 'joins multiple expressions with |' { 10 | InModuleScope 'PSKoans' { ConvertFrom-WildcardPattern 'AboutArrays', 'AboutComparison' } | 11 | Should -Be '^AboutArrays$|^AboutComparison$' 12 | } 13 | 14 | It 'replaces wildcard characters with regex equivalent' -TestCases @( 15 | @{ Pattern = 'About*son'; Expected = '^About.*son$' } 16 | @{ Pattern = 'AboutArrays*'; Expected = '^AboutArrays' } 17 | @{ Pattern = '*Arrays'; Expected = 'Arrays$' } 18 | @{ Pattern = '*Array*'; Expected = 'Array' } 19 | ) { 20 | InModuleScope 'PSKoans' -Parameters @{ Pattern = $Pattern } { 21 | param($Pattern) 22 | 23 | ConvertFrom-WildcardPattern $Pattern 24 | } | Should -Be $Expected 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Get-KoanAst.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-KoanAst' { 4 | 5 | BeforeAll { 6 | $path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' 7 | 8 | Set-Content -Path $path -Value @' 9 | using module PSKoans 10 | [Koan(Position = 1)] 11 | param() 12 | <# 13 | About Something 14 | #> 15 | Describe 'Something' { 16 | It 'Has some examples' { 17 | $true | Should -BeTrue 18 | } 19 | } 20 | '@ 21 | } 22 | 23 | It 'excludes the "using module PSKoans" statement from the AST' { 24 | $ast = InModuleScope 'PSKoans' -Parameters @{ Path = $path } { 25 | param($Path) 26 | Get-KoanAst -Path $Path 27 | } 28 | 29 | $ast.UsingStatements | Should -BeNullOrEmpty 30 | } 31 | 32 | It 'maintains consistency of position data when reading and modifying the source' { 33 | $tokens = $errors = $null 34 | $originalAst = [System.Management.Automation.Language.Parser]::ParseFile( 35 | $path, 36 | [Ref]$tokens, 37 | [Ref]$errors 38 | ) 39 | 40 | $originalItBlock = $originalAst.Find( 41 | { 42 | $args[0] -is [System.Management.Automation.Language.CommandAst] -and 43 | $args[0].GetCommandName() -eq 'It' 44 | }, 45 | $true 46 | ) 47 | 48 | $modifiedAst = InModuleScope 'PSKoans' -Parameters @{ Path = $path } { 49 | param($Path) 50 | Get-KoanAst -Path $Path 51 | } 52 | 53 | $modifiedItBlock = $modifiedAst.Find( 54 | { 55 | $args[0] -is [System.Management.Automation.Language.CommandAst] -and 56 | $args[0].GetCommandName() -eq 'It' 57 | }, 58 | $true 59 | ) 60 | 61 | $modifiedItBlock.Extent.StartOffset | 62 | Should -Be $originalItBlock.Extent.StartOffset -Because 'the start offsets should match' 63 | $modifiedItBlock.Extent.EndOffset | 64 | Should -Be $originalItBlock.Extent.EndOffset -Because 'the end offsets should match' 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Get-KoanIt.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-KoanIt' { 4 | 5 | BeforeAll { 6 | $defaultParams = @{ 7 | Path = Join-Path $TestDrive 'AboutSomething.Koans.ps1' 8 | } 9 | } 10 | 11 | Context 'Content has no errors' { 12 | 13 | BeforeAll { 14 | Mock 'Get-KoanAst' -ModuleName 'PSKoans' { 15 | { 16 | [Koan(Position = 1)] 17 | param( ) 18 | 19 | Describe 'About something' { 20 | It 'first' { 21 | $true | Should -BeTrue 22 | } 23 | 24 | It 'second' { 25 | $true | Should -BeTrue 26 | } 27 | } 28 | }.Ast 29 | } 30 | } 31 | 32 | It 'returns all information about all It blocks' { 33 | $ItCommands = InModuleScope 'PSKoans' -Parameters $defaultParams { 34 | param($Path) 35 | Get-KoanIt $Path 36 | } 37 | 38 | $ItCommands | Should -Not -BeNullOrEmpty 39 | $ItCommands.Count | Should -Be 2 40 | } 41 | } 42 | 43 | Context 'Content has errors' { 44 | 45 | BeforeAll { 46 | Set-Content @defaultParams -Value @' 47 | [Koan(Position = 1)] 48 | param( ) 49 | 50 | Describe 'About something' { 51 | It 'first' { 52 | -not ____ | Should -BeTrue 53 | } 54 | 55 | It 'second' { 56 | $true | Should -BeTrue 57 | } 58 | } 59 | '@ 60 | } 61 | 62 | It 'Retrieves all It blocks when the file has syntax errors' { 63 | $ItCommands = InModuleScope 'PSKoans' -Parameters $defaultParams { 64 | param($Path) 65 | Get-KoanIt $Path 66 | } 67 | 68 | $ItCommands | Should -Not -BeNullOrEmpty 69 | $ItCommands.Count | Should -Be 2 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Invoke-Koan.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Invoke-Koan' { 4 | 5 | BeforeAll { 6 | $testFile = @{ Script = "$PSScriptRoot/ControlTests/Invoke-Koan.Control_Tests.ps1" } 7 | } 8 | 9 | It 'runs the test successfully' { 10 | { 11 | InModuleScope 'PSKoans' -Parameters $testFile { 12 | param($Script) 13 | Invoke-Koan @{ Script = $Script } 14 | } 15 | } | Should -Not -Throw 16 | } 17 | 18 | It 'produces output with -Passthru' { 19 | InModuleScope 'PSKoans' -Parameters $testFile { 20 | param($Script) 21 | Invoke-Koan @{ Script = $Script; PassThru = $true } 22 | } | Should -Not -BeNullOrEmpty 23 | } 24 | 25 | It 'correctly reports test results' { 26 | $Results = InModuleScope 'PSKoans' -Parameters $testFile { 27 | param($Script) 28 | Invoke-Koan @{ Script = $Script; PassThru = $true } 29 | } 30 | 31 | $Results.TotalCount | Should -Be 2 32 | $Results.PassedCount | Should -Be 0 33 | $Results.FailedCount | Should -Be 2 34 | } 35 | 36 | It 'reports only expected exception types' { 37 | $Results = InModuleScope 'PSKoans' -Parameters $testFile { 38 | param($Script) 39 | Invoke-Koan @{ Script = $Script; PassThru = $true } 40 | } 41 | 42 | $Results.Tests.ErrorRecord.Exception | 43 | ForEach-Object -MemberName GetType | 44 | Should -Be @([Exception], [NotImplementedException]) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Measure-Koan.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Measure-Koan' -Skip { 4 | 5 | BeforeAll { 6 | Set-Content -Path 'TestDrive:\TestCases.Koans.ps1' -Value @' 7 | Describe 'Test cases param' { 8 | It 'first ' -TestCases @( 9 | @{ TestCase = 1 } 10 | @{ TestCase = 2 } 11 | @{ TestCase = 3 } 12 | ) { 13 | param($TestCase) 14 | 15 | $TestCase | Should -BeOfType int 16 | } 17 | } 18 | '@ 19 | 20 | Set-Content -Path 'TestDrive:\JustIt.Koans.ps1' -Value @' 21 | Describe 'Just it' { 22 | It 'first' { 23 | $true | Should -BeTrue 24 | } 25 | 26 | It 'second' { 27 | $true | Should -BeTrue 28 | } 29 | } 30 | '@ 31 | 32 | Set-Content -Path TestDrive:\Mixed.Koans.ps1 -Value @' 33 | Describe 'Mixed' { 34 | It 'first' { 35 | $true | Should -BeTrue 36 | } 37 | 38 | It 'second ' -TestCases @( 39 | @{ TestCase = 1 } 40 | @{ TestCase = 2 } 41 | ) { 42 | param($TestCase) 43 | 44 | $TestCase | Should -BeOfType int 45 | } 46 | } 47 | '@ 48 | 49 | Set-Content -Path TestDrive:\MutlipleTestCases.Koans.ps1 -Value @' 50 | Describe 'Test cases param' { 51 | It 'first ' -TestCases @( 52 | @{ TestCase = 1 } 53 | @{ TestCase = 2 } 54 | @{ TestCase = 3 } 55 | ) { 56 | param($TestCase) 57 | 58 | $TestCase | Should -BeOfType int 59 | } 60 | 61 | It 'second ' -TestCases @( 62 | @{ TestCase = 1 } 63 | @{ TestCase = 2 } 64 | @{ TestCase = 3 } 65 | ) { 66 | param($TestCase) 67 | 68 | $TestCase | Should -BeOfType int 69 | } 70 | } 71 | '@ 72 | } 73 | 74 | It 'correctly counts the number of tests in , including -TestCases' -TestCases @( 75 | @{ Path = 'TestDrive:\TestCases.Koans.ps1'; ExpectedValue = 3 } 76 | @{ Path = 'TestDrive:\JustIt.Koans.ps1'; ExpectedValue = 2 } 77 | @{ Path = 'TestDrive:\Mixed.Koans.ps1'; ExpectedValue = 3 } 78 | @{ Path = 'TestDrive:\MutlipleTestCases.Koans.ps1'; ExpectedValue = 6 } 79 | ) { 80 | $koanInfo = [PSCustomObject]@{ 81 | Path = $Path 82 | PSTypeName = 'PSKoans.KoanInfo' 83 | } 84 | 85 | InModuleScope 'PSKoans' -Parameters @{ Koan = $koanInfo } { 86 | param($Koan) 87 | Measure-Koan $Koan 88 | } | Should -Be $ExpectedValue 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/Functions/Private/New-KoanRunspace.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'New-KoanRunspace' { 2 | 3 | BeforeAll { 4 | [runspace]$runspace = $null 5 | } 6 | 7 | AfterEach { 8 | $runspace.Dispose() 9 | } 10 | 11 | It 'creates a new runspace' { 12 | $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } 13 | $runspace | Should -BeOfType [runspace] 14 | } 15 | 16 | It 'names the new runspace' { 17 | $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } 18 | $runspace.Name | Should -Be 'PSKoans.KoanRunspace' 19 | } 20 | 21 | It 'preloads the runspace with PSKoans' { 22 | $runspace = InModuleScope 'PSKoans' { New-KoanRunspace } 23 | $ps = [powershell]::Create($runspace) 24 | 25 | try { 26 | $ps.AddCommand('Get-Module').AddParameter('Name', 'PSKoans') > $null 27 | $module = $ps.Invoke() 28 | $module.Name | Should -eq 'PSKoans' 29 | } 30 | finally { 31 | $ps.Dispose() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/Functions/Private/New-PSKoanErrorRecord.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'New-PSKoanErrorRecord' { 4 | 5 | Context 'With Exception Object' { 6 | 7 | BeforeAll { 8 | $Output, $Parameters = InModuleScope 'PSKoans' { 9 | $Params = @{ 10 | Exception = [Exception]::new('Test message') 11 | ErrorId = 'Test.ErrorId' 12 | ErrorCategory = 'NotSpecified' 13 | TargetObject = $null 14 | } 15 | New-PSKoanErrorRecord @Params 16 | $Params 17 | } 18 | } 19 | 20 | It 'creates an ErrorRecord object' { 21 | $Output | Should -BeOfType System.Management.Automation.ErrorRecord 22 | } 23 | 24 | It 'emits the same type of exception' { 25 | $Output.Exception | Should -BeOfType $Parameters.Exception.GetType().FullName 26 | } 27 | 28 | It 'includes the input exception message' { 29 | $Output.Exception.Message | Should -BeExactly $Parameters.Exception.Message 30 | } 31 | 32 | It 'emits the correct error ID with "PSKoans" prefix' { 33 | $Output.FullyQualifiedErrorId | Should -BeExactly "PSKoans.$($Parameters.ErrorId)" 34 | } 35 | 36 | It 'assigns the requested error category' { 37 | $Output.CategoryInfo.Category | Should -Be $Parameters.ErrorCategory 38 | } 39 | 40 | It 'assigns the target object' { 41 | $Output.TargetObject | Should -Be $Params.TargetObject 42 | } 43 | } 44 | 45 | Context 'With TypeName and Message' { 46 | 47 | BeforeAll { 48 | $Output, $Parameters = InModuleScope 'PSKoans' { 49 | $Params = @{ 50 | ExceptionType = 'Exception' 51 | ExceptionMessage = 'Test message' 52 | ErrorId = 'Test.OtherErrorId' 53 | ErrorCategory = 'NotSpecified' 54 | TargetObject = $null 55 | } 56 | New-PSKoanErrorRecord @Params 57 | $Params 58 | } 59 | } 60 | 61 | It 'creates an ErrorRecord object' { 62 | $Output | Should -BeOfType System.Management.Automation.ErrorRecord 63 | } 64 | 65 | It 'creates the correct type of exception' { 66 | $Output.Exception | Should -BeOfType $Parameters.ExceptionType 67 | } 68 | 69 | It 'applies the requested exception message' { 70 | $Output.Exception.Message | Should -BeExactly $Parameters.ExceptionMessage 71 | } 72 | 73 | It 'emits the correct error ID with "PSKoans" prefix' { 74 | $Output.FullyQualifiedErrorId | Should -BeExactly "PSKoans.$($Parameters.ErrorId)" 75 | } 76 | 77 | It 'assigns the requested error category' { 78 | $Output.CategoryInfo.Category | Should -Be $Parameters.ErrorCategory 79 | } 80 | 81 | It 'assigns the target object' { 82 | $Output.TargetObject | Should -Be $Params.TargetObject 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Tests/Functions/Private/Update-PSKoanFile.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Update-PSKoanFile' { 4 | 5 | BeforeAll { 6 | Mock 'Get-PSKoanLocation' { 7 | Join-Path -Path $TestDrive -ChildPath 'Koans' 8 | } 9 | 10 | Mock 'Get-PSKoan' { 11 | [PSCustomObject]@{ 12 | Topic = 'AboutSomething' 13 | Path = Join-Path -Path $TestDrive -ChildPath 'Module/Group/AboutSomething.ps1' 14 | RelativePath = 'Group/AboutSomething.ps1' 15 | } 16 | } 17 | 18 | New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Koans/Group') -ItemType Directory 19 | New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'Module/Group') -ItemType Directory 20 | 21 | Set-Content -Path (Get-PSKoan).Path -Value @' 22 | Describe 'AboutSomething' { 23 | It 'koan 1' { 24 | __ | Should -Be 1 25 | } 26 | 27 | It 'koan 2' { 28 | __ | Should -Be 2 29 | } 30 | 31 | Context 'first' { 32 | It 'koan 3' { 33 | __ | Should -Be 3 34 | } 35 | } 36 | 37 | Context 'second' { 38 | It 'koan 4' { 39 | __ | Should -Be 4 40 | } 41 | } 42 | } 43 | '@ 44 | 45 | $userFilePath = Join-Path -Path (Get-PSKoanLocation) -ChildPath (Get-PSKoan).RelativePath 46 | } 47 | 48 | BeforeEach { 49 | Set-Content -Path $userFilePath -Value @' 50 | Describe 'AboutSomething' { 51 | It 'koan 1' { 52 | 1 | Should -Be 1 53 | } 54 | 55 | It 'koan 2' { 56 | __ | Should -Be 2 57 | } 58 | 59 | Context 'second' { 60 | It 'koan 4' { 61 | 4 | Should -Be 4 62 | } 63 | } 64 | } 65 | '@ 66 | } 67 | 68 | It 'should replay completed koans' { 69 | InModuleScope 'PSKoans' { Update-PSKoanFile -Topic AboutSomething -Confirm:$false } 70 | 71 | $userFilePath | Should -FileContentMatch '1 | Should -Be 1' 72 | $userFilePath | Should -FileContentMatch '__ | Should -Be 2' 73 | $userFilePath | Should -FileContentMatch '4 | Should -Be 4' 74 | } 75 | 76 | It 'should should allow new koans to be inserted' { 77 | InModuleScope 'PSKoans' { Update-PSKoanFile -Topic AboutSomething -Confirm:$false } 78 | 79 | $userFilePath | Should -FileContentMatch 'koan 3' 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Get-Blank.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-Blank' { 4 | 5 | It 'should not produce output' { 6 | Get-Blank | Should -BeNullOrEmpty 7 | } 8 | 9 | It 'should be usable in the middle of a pipeline' { 10 | { Get-Variable | Get-Blank | Write-Output } | Should -Not -Throw 11 | } 12 | 13 | It 'should quietly ignore parameters' { 14 | { Get-Blank -Param1 Value1 -Param2 Value2 } | Should -Not -Throw 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Get-PSKoan.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-PSKoan' { 4 | 5 | BeforeAll { 6 | Mock 'Get-PSKoanLocation' { 7 | Join-Path $TestDrive 'PSKoans' 8 | } 9 | 10 | Update-PSKoan -Confirm:$false 11 | 12 | # Stage test module 13 | $path = Join-Path -Path (Get-PSKoanLocation) 'Modules\TestModule' 14 | New-Item -Path $path -ItemType Directory -Force 15 | Set-Content -Path (Join-Path -Path $path -ChildPath 'AboutSomething.Koans.ps1') -Value @' 16 | using module PSKoans 17 | [Koan(Position = 1, Module = 'TestModule')] 18 | param() 19 | 20 | Describe 'AboutSomething' { 21 | It 'first' { 22 | $true | Should -BeTrue 23 | } 24 | } 25 | '@ 26 | } 27 | 28 | It 'retrieves all except module-specific koan files' { 29 | $Files = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.Koans.ps1 -Recurse -File | 30 | Where-Object FullName -NotMatch 'PSKoans[\\/]Modules[\\/]' 31 | 32 | (Get-PSKoan).Topic.Count | Should -Be $Files.Count 33 | } 34 | 35 | It 'retrieves specific requested files with -Topic ' -TestCases @( 36 | @{ Topic = 'AboutArrays' } 37 | @{ Topic = 'AboutVariables' } 38 | @{ Topic = 'AboutTypeOperators' } 39 | @{ Topic = 'AboutLists' } 40 | @{ Topic = 'AboutVariables', 'AboutLists' } 41 | ) { 42 | (Get-PSKoan -Topic $Topic).Topic | Should -Be $Topic 43 | } 44 | 45 | It 'retrieves specific requested files with -Module ' -TestCases @( 46 | @{ Topic = 'AboutSomething'; Module = 'TestModule' } 47 | ) { 48 | (Get-PSKoan -Module $Module -Scope User).Topic | Should -Be $Topic 49 | } 50 | 51 | It 'should throw a terminating error if a file is blocked' -Skip:($PSVersionTable.PSEdition -ne 'Desktop' -or $PSVersionTable.Platform -ne 'Win32NT') { 52 | $testFile = Get-ChildItem -Path (Get-PSKoanLocation) -Filter AboutArrays.Koans.ps1 -Recurse -File | 53 | Select-Object -First 1 54 | 55 | Set-Content -Path $testFile.FullName -Stream Zone.Identifier -Value @' 56 | [ZoneTransfer] 57 | ZoneId=3 58 | ReferrerUrl=C:\Downloads\File.zip 59 | '@ 60 | 61 | { Get-PSKoan -Topic AboutArrays -Scope User } | Should -Throw -ErrorId PSKoans.KoanFileIsBlocked 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Get-PSKoanLocation.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-PSKoanLocation' { 4 | 5 | Context 'Normal Behaviour' { 6 | 7 | BeforeAll { 8 | Mock 'Get-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } -MockWith { 9 | '~/PSKoans' 10 | } 11 | 12 | $Result = Get-PSKoanLocation 13 | } 14 | 15 | It 'retrieves the koan library location' { 16 | $Result | Should -Be '~/PSKoans' 17 | } 18 | 19 | It 'calls Get-PSKoanSetting with -Name "KoanLocation"' { 20 | Should -Invoke 'Get-PSKoanSetting' -Scope Context 21 | } 22 | } 23 | 24 | Context 'No Value Available' { 25 | 26 | BeforeAll { 27 | Mock 'Get-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } 28 | } 29 | 30 | It 'throws an error if no value can be retrieved' { 31 | { Get-PSKoanLocation } | Should -Throw -ExpectedMessage 'PSKoans folder location has not been defined' 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Get-PSKoanSetting.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Get-PSKoanSetting' { 4 | 5 | BeforeAll { 6 | $configFilePath = 'TestDrive:/config.json' 7 | InModuleScope 'PSKoans' -Parameters @{ Path = $configFilePath } { 8 | param($Path) 9 | $script:OldConfigPath = $script:ConfigPath 10 | $script:ConfigPath = $Path 11 | } 12 | 13 | ${/} = [IO.Path]::DirectorySeparatorChar 14 | } 15 | 16 | AfterAll { 17 | InModuleScope 'PSKoans' { 18 | $script:ConfigPath = $script:OldConfigPath 19 | } 20 | } 21 | 22 | Context 'Settings file does not exist' { 23 | 24 | BeforeAll { 25 | Mock 'Set-PSKoanSetting' -ParameterFilter { $Settings -is [hashtable] } 26 | $DefaultSettings = InModuleScope 'PSKoans' { $script:DefaultSettings } 27 | } 28 | ` 29 | It 'returns the default settings' { 30 | $Result = Get-PSKoanSetting 31 | $Result | Should -BeOfType [PSCustomObject] 32 | $Result.KoanLocation | Should -BeExactly "$HOME${/}PSKoans" 33 | $Result.Editor | Should -BeExactly 'code' 34 | } 35 | 36 | It 'calls Set-PSKoanSetting to set the default settings' { 37 | Should -Invoke 'Set-PSKoanSetting' -Scope Context 38 | } 39 | } 40 | 41 | Context 'Settings file does exist' { 42 | 43 | BeforeAll { 44 | [PSCustomObject]@{ 45 | KoanLocation = "TestLocation" 46 | Editor = "TestEditor" 47 | } | 48 | ConvertTo-Json | 49 | Set-Content -Path $configFilePath 50 | } 51 | 52 | It 'returns all settings if none are specified' { 53 | $Result = Get-PSKoanSetting 54 | $Result.KoanLocation | Should -BeExactly 'TestLocation' 55 | $Result.Editor | Should -BeExactly 'TestEditor' 56 | } 57 | 58 | It 'returns only the specified setting with -Name ' -TestCases @( 59 | @{ Name = 'KoanLocation'; Expected = 'TestLocation' } 60 | @{ Name = 'Editor'; Expected = 'TestEditor' } 61 | ) { 62 | Get-PSKoanSetting -Name $Name | Should -BeExactly $Expected 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Move-PSKoanLibrary' { 4 | 5 | Context 'Unit Tests with Mocks' { 6 | 7 | BeforeAll { 8 | $OriginalPath = New-Item -Path 'TestDrive:/PSKoans' -ItemType Directory | 9 | Select-Object -ExpandProperty FullName 10 | $TestPath = New-Item -ItemType Directory -Path 'TestDrive:/TestPath' | Join-Path -ChildPath 'Koans' 11 | 12 | Mock 'Get-PSKoanLocation' { $OriginalPath } 13 | Mock 'Set-PSKoanLocation' -ParameterFilter { $Path -eq $TestPath } 14 | Mock 'Move-Item' -ParameterFilter { $Path -eq $OriginalPath } -MockWith { $Destination } 15 | } 16 | 17 | It 'should output the new location' { 18 | Move-PSKoanLibrary -Path $TestPath | Should -BeExactly $TestPath 19 | } 20 | 21 | It 'should call Get-PSKoanLocation' { 22 | Should -Invoke 'Get-PSKoanLocation' -Scope Context 23 | } 24 | 25 | It 'should call Move-Item' { 26 | Should -Invoke 'Move-Item' -Scope Context 27 | } 28 | 29 | It 'should call Set-PSKoanLocation' { 30 | Should -Invoke 'Set-PSKoanLocation' -Scope Context 31 | } 32 | } 33 | 34 | Context 'Integration Tests' { 35 | 36 | BeforeAll { 37 | $OldLocation = Get-PSKoanLocation 38 | 39 | Set-PSKoanLocation -Path 'TestDrive:/PSKoans' 40 | Update-PSKoan -Confirm:$false 41 | 42 | $NewLocation = 'TestDrive:/NewLocation/PSKoans' 43 | New-Item -Path ($NewLocation | Split-Path -Parent) -ItemType Directory 44 | } 45 | 46 | AfterAll { 47 | Set-PSKoanLocation $OldLocation 48 | } 49 | 50 | It 'should move the folder to the new location' { 51 | Move-PSKoanLibrary -Path $NewLocation 52 | Test-Path $NewLocation -PathType Container | Should -BeTrue 53 | } 54 | 55 | It 'should update the KoanLocation' { 56 | Get-PSKoanLocation | Should -BeExactly (Get-Item -Path $NewLocation).FullName 57 | } 58 | 59 | It 'should copy all files intact to the new location' { 60 | $OriginalFileHashes = Get-PSKoanLocation | 61 | Get-ChildItem -Recurse -File | 62 | Get-FileHash | 63 | ForEach-Object { 64 | @{ 65 | File = ($_.Path -split [regex]::Escape([IO.Path]::DirectorySeparatorChar))[-2, -1] -join ':' 66 | Hash = $_.Hash 67 | } 68 | } 69 | 70 | $finalLocation = 'TestDrive:/FinalLocation/PSKoans' 71 | New-Item -Path ($finalLocation | Split-Path -Parent) -ItemType Directory 72 | Move-PSKoanLibrary -Path $finalLocation 73 | 74 | $newFileHashes = @{} 75 | Get-ChildItem -Path $finalLocation -Recurse -File -PipelineVariable Item | 76 | Get-FileHash | 77 | ForEach-Object { 78 | $newFileHashes[$Item.Name] = $_.Hash 79 | } 80 | 81 | foreach ($File in $OriginalFileHashes) { 82 | $FileName = ($File.File -split ':')[-1] 83 | $reason = "the hash of $($File.File) should match the new file hash" 84 | $newFileHashes[$FileName] | Should -BeExactly $File.Hash -Because $reason 85 | } 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Register-Advice.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe "Register-Advice" { 4 | 5 | Context "Profile Folder/File Missing" { 6 | 7 | BeforeAll { 8 | Mock New-Item -Verifiable 9 | Mock Test-Path { $false } -Verifiable 10 | Mock Set-Content -ParameterFilter { $Value -eq "Show-Advice" } -Verifiable 11 | } 12 | 13 | It 'should create the $profile if it does not exist' { 14 | Register-Advice 15 | Should -InvokeVerifiable 16 | } 17 | } 18 | 19 | Context "Profile Already Exists" { 20 | 21 | BeforeAll { 22 | Mock 'Test-Path' { $true } -Verifiable 23 | Mock 'Select-String' { $false } -Verifiable 24 | Mock 'Add-Content' -Verifiable 25 | } 26 | 27 | It "adds content to the profile if it already exists (Get|Set)-Advice" { 28 | Register-Advice 29 | Should -InvokeVerifiable 30 | } 31 | } 32 | 33 | Context "Parameter Validation" { 34 | 35 | BeforeAll { 36 | Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfileFolder } 37 | Mock New-Item 38 | Mock Test-Path { $false } -ParameterFilter { $Path -eq $ProfilePath } 39 | Mock Set-Content -ParameterFilter { $Value -eq "Show-Advice" } 40 | } 41 | 42 | It "throws if an invalid value is supplied for -TargetProfile" { 43 | { Register-Advice -TargetProfile "Invalidvalue" } | Should -Throw 44 | } 45 | 46 | It "works correctly with the profile" -TestCases @( 47 | @{ ProfilePath = 'AllUsersAllHosts' } 48 | @{ ProfilePath = 'AllUsersCurrentHost' } 49 | @{ ProfilePath = 'CurrentUserAllHosts' } 50 | @{ ProfilePath = 'CurrentUserCurrentHost' } 51 | ) { 52 | try { 53 | Register-Advice $ProfilePath | Should -BeNullOrEmpty 54 | } 55 | catch [UnauthorizedAccessException] { 56 | # Current user doesn't have access to the profile path. This can be normal for the 'AllUsers' paths. 57 | if ($ProfilePath -notmatch '^AllUsers') { 58 | throw $_ 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Set-PSKoanLocation.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Set-PSKoanLocation' { 4 | 5 | BeforeAll { 6 | Mock 'Set-PSKoanSetting' -ParameterFilter { $Name -eq 'KoanLocation' } 7 | } 8 | 9 | It 'outputs no data by default' { 10 | Set-PSKoanLocation -Path 'TestDrive:/PSKoans/' | Should -BeNullOrEmpty 11 | } 12 | 13 | It 'sets the KoanLocation setting' { 14 | Should -Invoke 'Set-PSKoanSetting' -Scope Describe 15 | } 16 | 17 | It 'returns the input -Path value back to the pipeline with -PassThru' { 18 | $ResolvedPath = Resolve-Path -Path '~' | Join-Path -ChildPath '/PSKoans/' 19 | Set-PSKoanLocation -Path '~/PSKoans/' -PassThru | Should -BeExactly $ResolvedPath 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Show-Advice.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe "Show-Advice" { 4 | 5 | BeforeAll { 6 | Mock 'Write-ConsoleLine' -ModuleName 'PSKoans' 7 | } 8 | 9 | Context "Behaviour of Parameter-less Calls" { 10 | 11 | BeforeAll { 12 | $result = Show-Advice 13 | } 14 | 15 | It "calls Write-ConsoleLine with Parameter -Title" { 16 | Should -Invoke 'Write-ConsoleLine' -ModuleName 'PSKoans' -ParameterFilter { $null -eq $Title } -Scope Context 17 | } 18 | 19 | It "calls Write-ConsoleLine with only the display string" { 20 | Should -Invoke 'Write-ConsoleLine' -ModuleName 'PSKoans' -ParameterFilter { $null -ne $Title } -Scope Context 21 | } 22 | 23 | It "outputs nothing to the pipeline" { 24 | $result | Should -BeNullOrEmpty 25 | } 26 | } 27 | 28 | Context "Behaviour with -Name Parameter" { 29 | 30 | BeforeAll { 31 | Show-Advice -Name "Profile" 32 | } 33 | 34 | It "should call Write-ConsoleLine with normal parameters" { 35 | Should -Invoke 'Write-ConsoleLine' -ParameterFilter { $null -ne $Title } -ModuleName 'PSKoans' -Scope Context 36 | } 37 | 38 | It "should call Write-ConsoleLine without parameters" { 39 | Should -Invoke 'Write-ConsoleLine' -ParameterFilter { $null -eq $Title } -ModuleName 'PSKoans' -Scope Context 40 | } 41 | 42 | It "should throw an error if the requested file cannot be found" { 43 | $message = "Could not find any Advice files matching the specified Name: ThisDoesntExist." 44 | { Show-Advice -name "ThisDoesntExist" -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message 45 | } 46 | } 47 | 48 | Context 'Behaviour with malformed advice files' { 49 | 50 | BeforeAll { 51 | $GetContentResult = [string]::Empty 52 | 53 | Mock Get-Content -MockWith { $GetContentResult } -Verifiable 54 | Mock Get-ChildItem -MockWith { [PSCustomObject]@{ PSPath = "DummyPath" } } -Verifiable 55 | } 56 | 57 | It "should throw an error if the requested file's format is not correct" -TestCases @( 58 | @{ 59 | Json = @{ 60 | NotTitle = "Fake title" 61 | NotContent = @(1..4 | ForEach-Object { "Fake line $_" }) 62 | } | ConvertTo-Json 63 | } 64 | @{ 65 | Json = @{ 66 | Content = @(1..4 | ForEach-Object { "Fake line $_" }) 67 | } | ConvertTo-Json 68 | } 69 | @{ 70 | Json = @{ 71 | Title = "Fake title" 72 | } | ConvertTo-Json 73 | } 74 | ) { 75 | $GetContentResult = $Json 76 | $AdviceName = "TestAdvice" 77 | $Message = "Could not find Title and/or Content elements for Advice file: {0}" -f $AdviceName 78 | { Show-Advice -name $AdviceName -ErrorAction Stop } | Should -Throw -ExpectedMessage $Message 79 | Should -InvokeVerifiable 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/Functions/Public/Update-PSKoan.Tests.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Modules PSKoans 2 | 3 | Describe 'Update-PSKoan' { 4 | 5 | Context 'Mocked Commands' { 6 | 7 | BeforeAll { 8 | Mock 'Remove-Item' 9 | Mock 'Copy-Item' 10 | Mock 'New-Item' 11 | Mock 'Move-Item' 12 | Mock 'Update-PSKoanFile' -ModuleName 'PSKoans' 13 | 14 | Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'Module' } -MockWith { 15 | [PSCustomObject]@{ 16 | Topic = 'Missing' 17 | Path = 'Module\Group\AboutSomethingMissing.Koans.ps1' 18 | } 19 | [PSCustomObject]@{ 20 | Topic = 'IncorrectPath' 21 | Path = 'Module\Group\AboutSomethingIncorrectPath.Koans.ps1' 22 | } 23 | [PSCustomObject]@{ 24 | Topic = 'Existing' 25 | Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' 26 | } 27 | } 28 | 29 | Mock 'Get-PSKoan' -ParameterFilter { $Scope -eq 'User' } -MockWith { 30 | [PSCustomObject]@{ 31 | Topic = 'IncorrectPath' 32 | Path = 'Module\RetiredGroup\AboutSomethingIncorrectPath.Koans.ps1' 33 | } 34 | [PSCustomObject]@{ 35 | Topic = 'RetiredTopic' 36 | Path = 'Module\RetiredGroup\AboutSomethingRetiredTopic.Koans.ps1' 37 | } 38 | [PSCustomObject]@{ 39 | Topic = 'Existing' 40 | Path = 'Module\Group\AboutSomethingExisting.Koans.ps1' 41 | } 42 | } 43 | } 44 | 45 | It 'should not produce output' { 46 | Update-PSKoan -Confirm:$false | Should -BeNullOrEmpty 47 | } 48 | 49 | It 'should copy missing topic files' { 50 | Should -Invoke 'Copy-Item' -Times 1 -Scope Context 51 | } 52 | 53 | It 'should move incorrectly placed topics' { 54 | Should -Invoke 'Remove-Item' -Times 1 -Scope Context 55 | } 56 | 57 | It 'should remove discarded topics' { 58 | Should -Invoke 'Remove-Item' -Times 1 -Scope Context 59 | } 60 | 61 | It 'should update topics which exist in module and koan path' { 62 | Should -Invoke 'Update-PSKoanFile' -ModuleName 'PSKoans' -Times 2 -Scope Context 63 | } 64 | } 65 | 66 | Context 'Practical Tests with TestDrive' { 67 | 68 | BeforeAll { 69 | Mock 'Get-PSKoanLocation' { 70 | Join-Path -Path $TestDrive -ChildPath 'PSKoans' 71 | } 72 | 73 | New-Item -Path (Get-PSKoanLocation) -ItemType Directory 74 | Update-PSKoan -Confirm:$false 75 | 76 | $file = Get-ChildItem -Path (Get-PSKoanLocation) -Filter *.koans.ps1 -File -Recurse | 77 | Select-Object -First 1 78 | } 79 | 80 | It 'should copy missing topic files' { 81 | $file | Remove-Item 82 | $file.FullName | Should -Not -Exist 83 | 84 | Update-PSKoan -Confirm:$false 85 | 86 | $file.FullName | Should -Exist 87 | } 88 | 89 | It 'should move incorrectly placed topics' { 90 | $directory = New-Item -Path (Join-Path -Path $TestDrive -ChildPath 'PSKoans\Wrong') -ItemType Directory 91 | $file | Move-Item -Destination $directory.FullName 92 | $file.FullName | Should -Not -Exist 93 | 94 | Update-PSKoan -Confirm:$false 95 | 96 | $file.FullName | Should -Exist 97 | } 98 | 99 | It 'should remove discarded topics' { 100 | $oldTopicPath = Join-Path $TestDrive 'PSKoans\Foundations\OldTopic.koans.ps1' 101 | $file | Copy-Item -Destination $oldTopicPath 102 | $oldTopicPath | Should -Exist 103 | 104 | Update-PSKoan -Confirm:$false 105 | 106 | $oldTopicPath | Should -Not -Exist 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Tests/ModuleHelp.Tests.ps1: -------------------------------------------------------------------------------- 1 | #region Discovery 2 | 3 | $ModuleName = 'PSKoans' 4 | 5 | #endregion Discovery 6 | 7 | BeforeAll { 8 | $ModuleName = 'PSKoans' 9 | Import-Module $ModuleName 10 | } 11 | 12 | Describe "$ModuleName Sanity Tests - Help Content" -Tags 'Module' { 13 | 14 | #region Discovery 15 | 16 | # The module will need to be imported during Discovery since we're using it to generate test cases / Context blocks 17 | Import-Module $ModuleName 18 | 19 | $ShouldProcessParameters = 'WhatIf', 'Confirm' 20 | 21 | # Generate command list for generating Context / TestCases 22 | $Module = Get-Module $ModuleName 23 | $CommandList = @( 24 | $Module.ExportedFunctions.Keys 25 | $Module.ExportedCmdlets.Keys 26 | ) 27 | 28 | #endregion Discovery 29 | 30 | foreach ($Command in $CommandList) { 31 | Context "$Command - Help Content" { 32 | 33 | #region Discovery 34 | 35 | $Help = @{ Help = Get-Help -Name $Command -Full | Select-Object -Property * } 36 | $Parameters = Get-Help -Name $Command -Parameter * -ErrorAction Ignore | 37 | Where-Object { $_.Name -and $_.Name -notin $ShouldProcessParameters } | 38 | ForEach-Object { 39 | @{ 40 | Name = $_.name 41 | Description = $_.Description.Text 42 | } 43 | } 44 | $Ast = @{ 45 | # Ast will be $null if the command is a compiled cmdlet 46 | Ast = (Get-Content -Path "function:/$Command" -ErrorAction Ignore).Ast 47 | Parameters = $Parameters 48 | } 49 | $Examples = $Help.Help.Examples.Example | ForEach-Object { @{ Example = $_ } } 50 | 51 | #endregion Discovery 52 | 53 | It "has help content for $Command" -TestCases $Help { 54 | $Help | Should -Not -BeNullOrEmpty 55 | } 56 | 57 | It "contains a synopsis for $Command" -TestCases $Help { 58 | $Help.Synopsis | Should -Not -BeNullOrEmpty 59 | } 60 | 61 | It "contains a description for $Command" -TestCases $Help { 62 | $Help.Description | Should -Not -BeNullOrEmpty 63 | } 64 | 65 | It "lists the function author in the Notes section for $Command" -TestCases $Help { 66 | $Notes = $Help.AlertSet.Alert.Text -split '\n' 67 | $Notes[0].Trim() | Should -BeLike "Author: *" 68 | } 69 | 70 | # This will be skipped for compiled commands ($Ast.Ast will be $null) 71 | It "has a help entry for all parameters of $Command" -TestCases $Ast -Skip:(-not ($Parameters -and $Ast.Ast)) { 72 | @($Parameters).Count | Should -Be $Ast.Body.ParamBlock.Parameters.Count -Because 'the number of parameters in the help should match the number in the function script' 73 | } 74 | 75 | It "has a description for $Command parameter -" -TestCases $Parameters -Skip:(-not $Parameters) { 76 | $Description | Should -Not -BeNullOrEmpty -Because "parameter $Name should have a description" 77 | } 78 | 79 | It "has at least one usage example for $Command" -TestCases $Help { 80 | $Help.Examples.Example.Code.Count | Should -BeGreaterOrEqual 1 81 | } 82 | 83 | It "lists a description for $Command example: " -TestCases $Examples { 84 | $Example.Remarks | Should -Not -BeNullOrEmpty -Because "example $($Example.Title) should have a description!" 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/ModuleValidation.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe 'Static Analysis: Module & Repository Files' { 2 | 3 | #region Discovery 4 | $FileSearch = @{ 5 | Path = Resolve-Path "$PSScriptRoot/.." 6 | Include = '*.ps1', '*.psm1', '*.psd1' 7 | Recurse = $true 8 | Exclude = '*.Koans.ps1' 9 | } 10 | $Scripts = Get-ChildItem @FileSearch 11 | 12 | $TestCases = $Scripts | ForEach-Object { @{ File = $_ } } 13 | #endregion Discovery 14 | 15 | Context 'Repository Code' { 16 | 17 | It 'has no invalid syntax errors in <File>' -TestCases $TestCases { 18 | $File.FullName | Should -Exist 19 | 20 | $FileContents = Get-Content -Path $File.FullName -ErrorAction Stop 21 | $Errors = $null 22 | [System.Management.Automation.PSParser]::Tokenize($FileContents, [ref]$Errors) > $null 23 | $Errors.Count | Should -Be 0 24 | } 25 | 26 | It 'has exactly one line feed at EOF in <File>' -TestCases $TestCases { 27 | $crlf = [Regex]::Match(($File | Get-Content -Raw), '(\r?(?<lf>\n))+\Z') 28 | $crlf.Groups['lf'].Captures.Count | Should -Be 1 29 | } 30 | } 31 | 32 | Context 'Module Import' { 33 | 34 | BeforeAll { 35 | $ModuleName = 'PSKoans' 36 | $ModuleRoot = (Get-Module -Name $ModuleName).ModuleBase 37 | } 38 | 39 | It 'cleanly imports the module' { 40 | { Import-Module (Join-Path $ModuleRoot "$ModuleName.psm1") -Force } | Should -Not -Throw 41 | } 42 | 43 | It 'removes and re-imports the module without errors' { 44 | $Script = { 45 | Remove-Module $ModuleName 46 | Import-Module (Join-Path -Path $ModuleRoot -ChildPath "$ModuleName.psm1") 47 | } 48 | 49 | $Script | Should -Not -Throw 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/Get-Blank.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Get-Blank.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-Blank 9 | 10 | ## SYNOPSIS 11 | 12 | Gets a blank item that does not equal anything. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Get-Blank [[-|PipeInput] <Object>] [[-|ParameterInput] <Object[]>] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Get-Blank returns an object of type [Blank] as defined in the PSKoans module. 23 | This object is not equivalent to any other type of object, including itself, when compared 24 | with a standard `-eq` comparison. 25 | 26 | The only exception, which is unavoidable, is that it is considered equal to $true when 27 | $true is on the left-hand side of the comparison. This kind of comparison may sometimes 28 | need to be carefully avoided when framing a koan assertion. 29 | 30 | For instance,an assertion such as `____ | Should -BeTrue` WILL pass, although it should not. 31 | 32 | ## EXAMPLES 33 | 34 | ### EXAMPLE 1 35 | 36 | ```powershell 37 | 38 | Get-Blank 39 | ``` 40 | 41 | Returns a blank object. 42 | 43 | ### EXAMPLE 2 44 | 45 | ```powershell 46 | __ 47 | ``` 48 | 49 | Returns a blank object. 50 | 51 | ## PARAMETERS 52 | 53 | ### -|PipeInput 54 | 55 | Used to capture the input in a pipeline context, to avoid erroring out in those contexts. 56 | This parameter is not intended to be used directly, and captures all pipeline input. 57 | 58 | ```yaml 59 | Type: Object 60 | Parameter Sets: (All) 61 | Aliases: 62 | 63 | Required: False 64 | Position: 1 65 | Default value: None 66 | Accept pipeline input: True (ByValue) 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -|ParameterInput 71 | 72 | Used to capture parameter names and arguments when used as a substitute for any other cmdlet. 73 | This parameter is not intended to be used directly, and collects all argument names and values. 74 | 75 | ```yaml 76 | Type: Object[] 77 | Parameter Sets: (All) 78 | Aliases: 79 | 80 | Required: False 81 | Position: 2 82 | Default value: None 83 | Accept pipeline input: False 84 | Accept wildcard characters: False 85 | ``` 86 | 87 | ### CommonParameters 88 | 89 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 90 | 91 | ## INPUTS 92 | 93 | ## OUTPUTS 94 | 95 | ### Blank 96 | 97 | An object that has no value, and is generally considered to be not equal to any value, including null. 98 | 99 | ## NOTES 100 | 101 | Author: Joel Sallow (@vexx32) 102 | 103 | ## RELATED LINKS 104 | 105 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 106 | -------------------------------------------------------------------------------- /docs/Get-PSKoanLocation.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanLocation.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-PSKoanLocation 9 | 10 | ## SYNOPSIS 11 | 12 | Gets the folder location where the current user's copy of the PSKoans lessons are stored. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Get-PSKoanLocation [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Gets the current value of the PSKoans working library path. 23 | This value defaults to `$HOME\PSKoans` but can be changed as you prefer. 24 | 25 | ## EXAMPLES 26 | 27 | ### EXAMPLE 1 28 | 29 | ```powershell 30 | Get-PSKoanLocation 31 | 32 | C:\Users\Timmy\PSKoans 33 | ``` 34 | 35 | ## PARAMETERS 36 | 37 | ### CommonParameters 38 | 39 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 40 | 41 | ## INPUTS 42 | 43 | ## OUTPUTS 44 | 45 | ### System.String 46 | 47 | The location used to store the user's koan topic files. 48 | 49 | ## NOTES 50 | 51 | Author: Joel Sallow (@vexx32) 52 | 53 | ## RELATED LINKS 54 | 55 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 56 | -------------------------------------------------------------------------------- /docs/Get-PSKoanSetting.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanSetting.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-PSKoanSetting 9 | 10 | ## SYNOPSIS 11 | 12 | Retrieves the configuration settings for PSKoans. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Get-PSKoanSetting [[-Name] <String>] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Retrieves configuration data from the locally stored json file in `$HOME/.config/PSKoans`. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | 28 | ```powershell 29 | Get-PSKoanSetting 30 | ``` 31 | 32 | Retrieves all module settings. 33 | 34 | ### EXAMPLE 2 35 | 36 | ```powershell 37 | Get-PSKoanSetting -Name LibraryFolder 38 | ``` 39 | 40 | Retrieves the library folder location (also retrievable with `Get-PSKoanLocation`). 41 | 42 | ### EXAMPLE 3 43 | 44 | ```powershell 45 | Get-PSKoanSetting -Name Editor 46 | ``` 47 | 48 | Retrieves the text editor that PSKoans will use for `Show-Karma -Contemplate`. 49 | 50 | ## PARAMETERS 51 | 52 | ### -Name 53 | 54 | Specifies which setting value to retrieve. 55 | 56 | ```yaml 57 | Type: String 58 | Parameter Sets: (All) 59 | Aliases: 60 | 61 | Required: False 62 | Position: 0 63 | Default value: None 64 | Accept pipeline input: False 65 | Accept wildcard characters: False 66 | ``` 67 | 68 | ### CommonParameters 69 | 70 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 71 | 72 | ## INPUTS 73 | 74 | ## OUTPUTS 75 | 76 | ### System.String 77 | 78 | When a specific setting is requested, returns only the value of that setting, which will usually be a string. 79 | 80 | ### System.Management.Automation.PSObject 81 | 82 | When all settings are requested, returns an object with properties representing each setting. 83 | 84 | ## NOTES 85 | 86 | Author: Joel Sallow (@vexx32) 87 | 88 | ## RELATED LINKS 89 | 90 | [Set-PSKoanSetting](https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanSetting.md) 91 | 92 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 93 | -------------------------------------------------------------------------------- /docs/Move-PSKoanLibrary.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Move-PSKoanLibrary.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Move-PSKoanLibrary 9 | 10 | ## SYNOPSIS 11 | 12 | Move your entire current PSKoans library folder to another location and update your KoanLocation setting to reflect the new location. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Move-PSKoanLibrary [-Path] <String> [-WhatIf] [-Confirm] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | `Move-PSKoanLibrary` takes your current PSKoans library location and moves the folder to the specified destination. 23 | Then, it updates the current KoanLocation setting to point to the new location. 24 | 25 | ## EXAMPLES 26 | 27 | ### Example 28 | 29 | ```powershell 30 | PS C:\> Move-PSKoanLibrary -Path C:\Users\Joe\OneDrive 31 | ``` 32 | 33 | Moves Joe's koan library into his OneDrive directory. 34 | 35 | ## PARAMETERS 36 | 37 | ### -Confirm 38 | 39 | Prompts you for confirmation before running the cmdlet. 40 | 41 | ```yaml 42 | Type: SwitchParameter 43 | Parameter Sets: (All) 44 | Aliases: cf 45 | 46 | Required: False 47 | Position: Named 48 | Default value: None 49 | Accept pipeline input: False 50 | Accept wildcard characters: False 51 | ``` 52 | 53 | ### -Path 54 | 55 | The path to the new library location. 56 | This path can be relative to the current session location, but cannot contain wildcards. 57 | 58 | ```yaml 59 | Type: String 60 | Parameter Sets: (All) 61 | Aliases: PSPath, Folder, Destination, TargetPath 62 | 63 | Required: True 64 | Position: 0 65 | Default value: None 66 | Accept pipeline input: True (ByValue) 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -WhatIf 71 | 72 | Shows what would happen if the cmdlet runs. 73 | The cmdlet is not run. 74 | 75 | ```yaml 76 | Type: SwitchParameter 77 | Parameter Sets: (All) 78 | Aliases: wi 79 | 80 | Required: False 81 | Position: Named 82 | Default value: None 83 | Accept pipeline input: False 84 | Accept wildcard characters: False 85 | ``` 86 | 87 | ### CommonParameters 88 | 89 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 90 | 91 | ## INPUTS 92 | 93 | ### None 94 | 95 | ## OUTPUTS 96 | 97 | ### System.Void 98 | 99 | ## NOTES 100 | 101 | Author: Joel Sallow (@vexx32) 102 | 103 | ## RELATED LINKS 104 | 105 | [Set-PSKoanSetting](https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanSetting.md) 106 | 107 | [Get-PSKoanSetting](https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanSetting.md) 108 | 109 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 110 | -------------------------------------------------------------------------------- /docs/PSKoans.md: -------------------------------------------------------------------------------- 1 | --- 2 | Module Name: PSKoans 3 | Module Guid: 45003073-0315-4aef-862f-4d9ff1bd8f4a 4 | Download Help Link: 5 | Help Version: 0.50.0.0 6 | Locale: en-US 7 | --- 8 | 9 | # PSKoans Module 10 | 11 | ## Description 12 | 13 | Inspired by Chris Marinos's **fantastic** [F# koans](https://github.com/ChrisMarinos/FSharpKoans), the goal of the PowerShell koans is to teach you PowerShell by presenting you with a set of questions. 14 | Each [kōan](https://en.wikipedia.org/wiki/K%C5%8Dan) (each question) is represented by a failing Pester test. 15 | Your goal is to make those tests pass by filling out the correct answer, or writing the correct code. 16 | The koans start very simple to get you familiar with the basic concepts and progress towards more difficult. 17 | Teaching you multiple beginner, intermediate and advanced aspects of PowerShell in the process. 18 | 19 | ## PSKoans Cmdlets 20 | 21 | ### [Get-Blank](Get-Blank.md) 22 | 23 | An auxiliary command used primarily in the koan files itself. 24 | It is most commonly used in its alias form `__`, representing a blank space. 25 | This command returns a blank item that never equals anything. 26 | 27 | ### [Get-Karma](Get-Karma.md) 28 | 29 | Use this command to assess your own progress and check how you're progressing. 30 | This command will output a simple report on your current progress. 31 | 32 | ### [Get-PSKoan](Get-PSKoan.md) 33 | 34 | Use this command to list all koan files available. 35 | The report will contain location information for each file in both the module and user paths. 36 | 37 | ### [Get-PSKoanLocation](Get-PSKoanLocation.md) 38 | 39 | Retrieves the currently set PSKoans working folder path. 40 | By default, this is set to `$HOME\PSKoans`. 41 | 42 | ### [Get-PSKoanSetting](Get-PSKoanSetting.md) 43 | 44 | Gets all configurable PSKoans settings. 45 | Specify `-Name` to retrieve the value of a specific setting. 46 | 47 | ### [Move-PSKoanLibrary](Move-PSKoanLibrary.md) 48 | 49 | Moves the entire library folder to a new location, and updates the KoanLocation configuration value accordingly. 50 | 51 | ### [Register-Advice](Register-Advice.md) 52 | 53 | Registers the Show-Advice command in your profile in order to display advice on startup. 54 | 55 | ### [Reset-PSKoan](Reset-PSKoan.md) 56 | 57 | Resets a given koan file, topic, context section, or individual koan to the default state. 58 | Use this to restore files damaged beyond repair or to reset a koan to its base state so you can try again. 59 | The default is to reset everything (with confirmation prompts); use parameters to narrow the scope. 60 | 61 | ### [Set-PSKoanLocation](Set-PSKoanLocation.md) 62 | 63 | Sets the PSKoans working folder path for the current session. 64 | 65 | ### [Set-PSKoanSetting](Set-PSKoanSetting.md) 66 | 67 | Configure available PSKoans settings, such as the editor used for `Show-Karma -Contemplate` and the user's library location. 68 | 69 | ### [Show-Advice](Show-Advice.md) 70 | 71 | Displays a random piece of advice from the PSKoans advice library on the screen. 72 | 73 | ### [Show-Karma](Show-Karma.md) 74 | 75 | Use this command to assess your own progress and check how you're progressing. 76 | This command will display a detailed report with flavour-text to guide your progress. 77 | 78 | ### [Update-PSKoan](Update-PSKoan.md) 79 | 80 | Updates selected topics, context blocks, and/or individual koan `It` blocks. 81 | This command is provided to smooth transition to new versions of PSKoans, and to update older files. 82 | By default, all files will be updated; specify parameters to narrow the scope. 83 | -------------------------------------------------------------------------------- /docs/Register-Advice.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Register-Advice.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Register-Advice 9 | 10 | ## SYNOPSIS 11 | 12 | Causes powershell to write a random piece of advice on each start. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Register-Advice [[-TargetProfile] <String>] [-WhatIf] [-Confirm] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Causes powershell to write a random piece of advice on each start. 23 | This is done by creating / modifying the powershell profile to call `Show-Advice` on each session start. 24 | 25 | ## EXAMPLES 26 | 27 | ### EXAMPLE 1 28 | 29 | ```powershell 30 | Register-Advice 31 | ``` 32 | 33 | Causes powershell to write a random piece of advice on each start. 34 | 35 | ## PARAMETERS 36 | 37 | ### -Confirm 38 | 39 | Prompts you for confirmation before modifying your profile. 40 | 41 | ```yaml 42 | Type: SwitchParameter 43 | Parameter Sets: (All) 44 | Aliases: cf 45 | 46 | Required: False 47 | Position: Named 48 | Default value: None 49 | Accept pipeline input: False 50 | Accept wildcard characters: False 51 | ``` 52 | 53 | ### -TargetProfile 54 | 55 | Specify a named profile to modify. 56 | 57 | ```yaml 58 | Type: String 59 | Parameter Sets: (All) 60 | Aliases: 61 | Accepted values: AllUsersAllHosts, AllUsersCurrentHost, CurrentUserAllHosts, CurrentUserCurrentHost 62 | 63 | Required: False 64 | Position: 0 65 | Default value: CurrentUserCurrentHost 66 | Accept pipeline input: False 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -WhatIf 71 | 72 | Shows what would happen if the cmdlet runs. 73 | The cmdlet is not run. 74 | 75 | ```yaml 76 | Type: SwitchParameter 77 | Parameter Sets: (All) 78 | Aliases: wi 79 | 80 | Required: False 81 | Position: Named 82 | Default value: None 83 | Accept pipeline input: False 84 | Accept wildcard characters: False 85 | ``` 86 | 87 | ### CommonParameters 88 | 89 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 90 | 91 | ## INPUTS 92 | 93 | ## OUTPUTS 94 | 95 | ### System.Void 96 | 97 | ## NOTES 98 | 99 | Author: Friedrich Weinmann (@FriedrichWeinmann) 100 | 101 | ## RELATED LINKS 102 | 103 | [Show-Advice](https://github.com/vexx32/PSKoans/tree/main/docs/Show-Advice.md) 104 | 105 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 106 | -------------------------------------------------------------------------------- /docs/Set-PSKoanLocation.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanLocation.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-PSKoanLocation 9 | 10 | ## SYNOPSIS 11 | 12 | Sets the PSKoans folder location where koan lesson files will be stored and retrieved. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Set-PSKoanLocation [-Path] <String> [-PassThru] [-WhatIf] [-Confirm] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Sets the `KoanLocation` configuration setting in order to modify where the module looks for and stores its koan lesson files. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | 28 | ```powershell 29 | Set-PSKoanLocation -Path C:\PSKoans 30 | 31 | Measure-Karma 32 | ``` 33 | 34 | Sets the koan folder location to 'C:\PSKoans' and then invokes Measure-Karma to examine that location for koan files. 35 | 36 | ## PARAMETERS 37 | 38 | ### -Confirm 39 | 40 | Prompts for confirmation before changing the koan location. 41 | 42 | ```yaml 43 | Type: SwitchParameter 44 | Parameter Sets: (All) 45 | Aliases: cf 46 | 47 | Required: False 48 | Position: Named 49 | Default value: None 50 | Accept pipeline input: False 51 | Accept wildcard characters: False 52 | ``` 53 | 54 | ### -PassThru 55 | 56 | Whether the function should pass the provided `-Path` value down the pipe when the configuration has been changed. 57 | 58 | ```yaml 59 | Type: SwitchParameter 60 | Parameter Sets: (All) 61 | Aliases: 62 | 63 | Required: False 64 | Position: Named 65 | Default value: None 66 | Accept pipeline input: False 67 | Accept wildcard characters: False 68 | ``` 69 | 70 | ### -Path 71 | 72 | Specify the path to set the koan location to. 73 | 74 | ```yaml 75 | Type: String 76 | Parameter Sets: (All) 77 | Aliases: PSPath, Folder 78 | 79 | Required: True 80 | Position: 0 81 | Default value: None 82 | Accept pipeline input: False 83 | Accept wildcard characters: False 84 | ``` 85 | 86 | ### -WhatIf 87 | 88 | Shows what would happen if the cmdlet runs. 89 | The cmdlet is not run. 90 | 91 | ```yaml 92 | Type: SwitchParameter 93 | Parameter Sets: (All) 94 | Aliases: wi 95 | 96 | Required: False 97 | Position: Named 98 | Default value: None 99 | Accept pipeline input: False 100 | Accept wildcard characters: False 101 | ``` 102 | 103 | ### CommonParameters 104 | 105 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 106 | 107 | ## INPUTS 108 | 109 | ## OUTPUTS 110 | 111 | ### System.Void 112 | 113 | ### System.String 114 | 115 | If `-PassThru` is specified, the command will emit the input path value back to the output stream. 116 | 117 | ## NOTES 118 | 119 | Author: Joel Sallow (@vexx32) 120 | 121 | The PSKoans folder specified will become the location to look for koans files. 122 | If this location is empty or nonexistent, it will be created and populated with a pristine copy of the koans library when Measure-Karma is run next. 123 | 124 | You can optionally populate it yourself by running `Show-Karma -Reset` following use of this cmdlet. 125 | 126 | ## RELATED LINKS 127 | 128 | [Get-PSKoanLocation](https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanLocation.md) 129 | 130 | [Move-PSKoanLibrary](https://github.com/vexx32/PSKoans/tree/main/docs/Move-PSKoanLibrary.md) 131 | 132 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 133 | -------------------------------------------------------------------------------- /docs/Set-PSKoanSetting.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Set-PSKoanSetting.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-PSKoanSetting 9 | 10 | ## SYNOPSIS 11 | 12 | Modifies the configuration settings for PSKoans. 13 | 14 | ## SYNTAX 15 | 16 | ### Single (Default) 17 | 18 | ```powershell 19 | Set-PSKoanSetting [-Name] <String> [-Value] <Object> [-WhatIf] [-Confirm] [<CommonParameters>] 20 | ``` 21 | 22 | ### Multiple 23 | 24 | ```powershell 25 | Set-PSKoanSetting [-Settings] <Hashtable> [-WhatIf] [-Confirm] [<CommonParameters>] 26 | ``` 27 | 28 | ### Reset 29 | 30 | ```powershell 31 | Set-PSKoanSetting [-Reset] [-WhatIf] [-Confirm] [<CommonParameters>] 32 | ``` 33 | 34 | ## DESCRIPTION 35 | 36 | Sets module configuration data in a JSON file in the user's $HOME directory. 37 | 38 | ## EXAMPLES 39 | 40 | ### EXAMPLE 1 41 | 42 | ```powershell 43 | Set-PSKoanSetting -Name LibraryFolder -Value "./PSKoans" 44 | ``` 45 | 46 | Sets the library folder location to the `PSKoans` folder in the current directory. 47 | 48 | ### EXAMPLE 2 49 | 50 | ```powershell 51 | Set-PSKoanSetting -Name Editor -Value "atom" 52 | ``` 53 | 54 | Sets the text editor used for `Show-Karma -Contemplate` to GitHub Atom. For a 55 | list of text editors known to PSKoans, see Example 2 in the documentation for 56 | [Show-Karma -Contemplate](https://github.com/vexx32/PSKoans/tree/main/docs/Show-Karma.md). 57 | 58 | ## PARAMETERS 59 | 60 | ### -Confirm 61 | 62 | Prompts you for confirmation before running the cmdlet. 63 | 64 | ```yaml 65 | Type: SwitchParameter 66 | Parameter Sets: (All) 67 | Aliases: cf 68 | 69 | Required: False 70 | Position: Named 71 | Default value: None 72 | Accept pipeline input: False 73 | Accept wildcard characters: False 74 | ``` 75 | 76 | ### -Name 77 | 78 | Specifies which setting value to modify. 79 | 80 | ```yaml 81 | Type: String 82 | Parameter Sets: Single 83 | Aliases: 84 | 85 | Required: True 86 | Position: 0 87 | Default value: None 88 | Accept pipeline input: True (ByPropertyName) 89 | Accept wildcard characters: False 90 | ``` 91 | 92 | ### -Reset 93 | 94 | Resets the user's settings to the default values. 95 | 96 | ```yaml 97 | Type: SwitchParameter 98 | Parameter Sets: Reset 99 | Aliases: 100 | 101 | Required: False 102 | Position: Named 103 | Default value: None 104 | Accept pipeline input: False 105 | Accept wildcard characters: False 106 | ``` 107 | 108 | ### -Settings 109 | 110 | A hashtable containing one or more settings to modify and their values. 111 | 112 | ```yaml 113 | Type: Hashtable 114 | Parameter Sets: Multiple 115 | Aliases: 116 | 117 | Required: True 118 | Position: 0 119 | Default value: None 120 | Accept pipeline input: True (ByValue) 121 | Accept wildcard characters: False 122 | ``` 123 | 124 | ### -Value 125 | 126 | Provides a value to apply to the target setting. 127 | 128 | ```yaml 129 | Type: Object 130 | Parameter Sets: Single 131 | Aliases: 132 | 133 | Required: True 134 | Position: 1 135 | Default value: None 136 | Accept pipeline input: True (ByPropertyName) 137 | Accept wildcard characters: False 138 | ``` 139 | 140 | ### -WhatIf 141 | 142 | Shows what would happen if the cmdlet runs. 143 | The cmdlet is not run. 144 | 145 | ```yaml 146 | Type: SwitchParameter 147 | Parameter Sets: (All) 148 | Aliases: wi 149 | 150 | Required: False 151 | Position: Named 152 | Default value: None 153 | Accept pipeline input: False 154 | Accept wildcard characters: False 155 | ``` 156 | 157 | ### CommonParameters 158 | 159 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 160 | 161 | ## INPUTS 162 | 163 | ## OUTPUTS 164 | 165 | ### System.Void 166 | 167 | ## NOTES 168 | 169 | Author: Joel Sallow (@vexx32) 170 | 171 | ## RELATED LINKS 172 | 173 | [Get-PSKoanSetting](https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoanSetting.md) 174 | 175 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 176 | -------------------------------------------------------------------------------- /docs/Show-Advice.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: https://github.com/vexx32/PSKoans/tree/main/docs/Show-Advice.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Show-Advice 9 | 10 | ## SYNOPSIS 11 | 12 | Prints a piece of advice to the screen. 13 | 14 | ## SYNTAX 15 | 16 | ```powershell 17 | Show-Advice [[-Name] <String>] [<CommonParameters>] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | 22 | Prints a piece of advice to the screen. 23 | Advice snippets are stored in a small library file in the module folder. 24 | 25 | ## EXAMPLES 26 | 27 | ### EXAMPLE 1 28 | 29 | ```powershell 30 | Get-Advice 31 | ``` 32 | 33 | Print a random piece of advice to the screen. 34 | 35 | ## PARAMETERS 36 | 37 | ### -Name 38 | 39 | The title or name of the specific advice snippet to display. 40 | 41 | ```yaml 42 | Type: String 43 | Parameter Sets: (All) 44 | Aliases: 45 | 46 | Required: False 47 | Position: 0 48 | Default value: * 49 | Accept pipeline input: False 50 | Accept wildcard characters: False 51 | ``` 52 | 53 | ### CommonParameters 54 | 55 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 56 | 57 | ## INPUTS 58 | 59 | ## OUTPUTS 60 | 61 | ### System.Void 62 | 63 | While the command does emit text, it is only written to the Information stream and the host. 64 | No data is emitted to the default output stream. 65 | 66 | ## NOTES 67 | 68 | Author: Friedrich Weinmann (@FriedrichWeinmann) 69 | 70 | ## RELATED LINKS 71 | 72 | [Register-Advice](https://github.com/vexx32/PSKoans/tree/main/docs/Register-Advice.md) 73 | 74 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 75 | -------------------------------------------------------------------------------- /docs/Update-PSKoan.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: PSKoans-help.xml 3 | Module Name: PSKoans 4 | online version: 5 | schema: 2.0.0 6 | --- 7 | 8 | # Update-PSKoan 9 | 10 | ## SYNOPSIS 11 | 12 | Update the user Koan directory with new topics and koans. 13 | 14 | ## SYNTAX 15 | 16 | ### TopicOnly (Default) 17 | 18 | ```powershell 19 | Update-PSKoan [-Topic <String[]>] [-WhatIf] [-Confirm] [<CommonParameters>] 20 | ``` 21 | 22 | ### ModuleOnly 23 | 24 | ```powershell 25 | Update-PSKoan [-Topic <String[]>] -Module <String[]> [-WhatIf] [-Confirm] [<CommonParameters>] 26 | ``` 27 | 28 | ### IncludeModule 29 | 30 | ```powershell 31 | Update-PSKoan [-Topic <String[]>] -IncludeModule <String[]> [-WhatIf] [-Confirm] [<CommonParameters>] 32 | ``` 33 | 34 | ## DESCRIPTION 35 | 36 | Update the user Koan directory with new topics. 37 | Topics will be moved to new directories if appropriate. 38 | Old files will be removed. 39 | 40 | Existing koan topics are updated with new koans. 41 | Progress is preserved as much as possible. 42 | 43 | ## EXAMPLES 44 | 45 | ### Example 1 46 | 47 | ```powershell 48 | PS C:\> Update-PSKoan -Topic AboutCompareObject 49 | ``` 50 | 51 | The topic AboutCompareObject will be added if it is not already present. 52 | If it is already present, the current copy will be compared to the base module copy. 53 | If any koans are missing from the user's copy, they will be added. 54 | If any koans have been removed from the module copy, they will be removed from the user's copy. 55 | 56 | ### Example 2 57 | 58 | ```powershell 59 | PS C:\> Update-PSKoan 60 | ``` 61 | 62 | All missing topics and koans will be copied from the module. 63 | 64 | ## PARAMETERS 65 | 66 | ### -Confirm 67 | 68 | Prompts you for confirmation before running the cmdlet. 69 | 70 | ```yaml 71 | Type: SwitchParameter 72 | Parameter Sets: (All) 73 | Aliases: cf 74 | 75 | Required: False 76 | Position: Named 77 | Default value: None 78 | Accept pipeline input: False 79 | Accept wildcard characters: False 80 | ``` 81 | 82 | ### -IncludeModule 83 | 84 | Update the default PowerShell Koans as well as Koans for the specified module. 85 | Wildcards are supported. 86 | 87 | ```yaml 88 | Type: String[] 89 | Parameter Sets: IncludeModule 90 | Aliases: 91 | 92 | Required: True 93 | Position: Named 94 | Default value: None 95 | Accept pipeline input: False 96 | Accept wildcard characters: True 97 | ``` 98 | 99 | ### -Module 100 | 101 | Update Koans in the specified module only. 102 | Wildcards are supported. 103 | 104 | ```yaml 105 | Type: String[] 106 | Parameter Sets: ModuleOnly 107 | Aliases: 108 | 109 | Required: True 110 | Position: Named 111 | Default value: None 112 | Accept pipeline input: False 113 | Accept wildcard characters: True 114 | ``` 115 | 116 | ### -Topic 117 | 118 | Updates the specified topic from the module. 119 | Wildcards are supported. 120 | 121 | ```yaml 122 | Type: String[] 123 | Parameter Sets: (All) 124 | Aliases: Koan, File 125 | 126 | Required: False 127 | Position: Named 128 | Default value: None 129 | Accept pipeline input: False 130 | Accept wildcard characters: True 131 | ``` 132 | 133 | ### -WhatIf 134 | 135 | Shows what would happen if the cmdlet runs. 136 | The cmdlet is not run. 137 | 138 | ```yaml 139 | Type: SwitchParameter 140 | Parameter Sets: (All) 141 | Aliases: wi 142 | 143 | Required: False 144 | Position: Named 145 | Default value: None 146 | Accept pipeline input: False 147 | Accept wildcard characters: False 148 | ``` 149 | 150 | ### CommonParameters 151 | 152 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 153 | 154 | ## INPUTS 155 | 156 | ### None 157 | 158 | ## OUTPUTS 159 | 160 | ### System.Void 161 | 162 | ## NOTES 163 | 164 | Author: Chris Dent (@indented-automation) 165 | 166 | ## RELATED LINKS 167 | 168 | [Get-PSKoan](https://github.com/vexx32/PSKoans/tree/main/docs/Get-PSKoan.md) 169 | 170 | [Reset-PSKoan](https://github.com/vexx32/PSKoans/tree/main/docs/Reset-PSKoan.md) 171 | 172 | [PSKoans](https://github.com/vexx32/PSKoans/tree/main/docs/PSKoans.md) 173 | -------------------------------------------------------------------------------- /drafts/AboutOOPAnswers/AboutOOPAnswers.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'AboutOOPAnswers' 3 | # 4 | # Generated by: Sudoblark 5 | # 6 | # Generated on: 20/08/2019 7 | # 8 | @{ 9 | 10 | # Script module or binary module file associated with this manifest 11 | RootModule = 'AboutOOPAnswers.psm1' 12 | 13 | # Version number of this module. 14 | ModuleVersion = '1.0' 15 | 16 | # ID used to uniquely identify this module 17 | GUID = 'd0a9150d-b6a4-4b17-a325-e3a24fed0aa9' 18 | 19 | # Author of this module 20 | Author = 'Sudoblark' 21 | 22 | # Company or vendor of this module 23 | CompanyName = 'Sudoblark' 24 | 25 | # Copyright statement for this module 26 | Copyright = '(c) 2019 Sudoblark. All rights reserved.' 27 | 28 | # Description of the functionality provided by this module 29 | Description = 'This module provides functions to verify answers given for AboutOOPAnswers 30 | related PSKoans' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | # PowerShellVersion = '' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of the .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = 'OOPTestAnswers' 70 | 71 | # Cmdlets to export from this module 72 | CmdletsToExport = '' 73 | 74 | # Variables to export from this module 75 | VariablesToExport = '' 76 | 77 | # Aliases to export from this module 78 | AliasesToExport = '' 79 | 80 | # List of all modules packaged with this module 81 | # ModuleList = @() 82 | 83 | # List of all files packaged with this module 84 | # FileList = @() 85 | 86 | # Private data to pass to the module specified in RootModule/ModuleToProcess 87 | # PrivateData = '' 88 | 89 | # HelpInfo URI of this module 90 | # HelpInfoURI = '' 91 | 92 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 93 | # DefaultCommandPrefix = '' 94 | 95 | } 96 | -------------------------------------------------------------------------------- /drafts/AboutOOPAnswers/AboutOOPAnswers.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | This is the answer file for PSKoans\IntroTraining\AboutOOP.koans.ps1 4 | 5 | It provides a single function which, when given a test, will provide the correct answer. 6 | 7 | #> 8 | enum Test { 9 | NorwayFjordsSchematics 10 | Eyjafjallajokull 11 | CoventrySchematics 12 | Mars 13 | doggoNose 14 | doggoBark 15 | doggoWoof 16 | doggoTail 17 | phoneVibrating 18 | keyboardColour 19 | lightPower 20 | toast 21 | } 22 | 23 | $ResultsHash = @{ 24 | 25 | [Test]::NorwayFjordsSchematics = 'object'; 26 | [Test]::Eyjafjallajokull = 'instance'; 27 | [Test]::CoventrySchematics = 'object'; 28 | [Test]::Mars = 'instance'; 29 | [Test]::doggoNose = $true; 30 | [Test]::doggoBark = $false; 31 | [Test]::doggoWoof = $true; 32 | [Test]::doggoTail = $true; 33 | [Test]::phoneVibrating = $true; 34 | [Test]::keyboardColour = $false; 35 | [Test]::lightPower = $false; 36 | [Test]::toast = $true; 37 | } 38 | function OOPTestAnswers { 39 | param( 40 | [parameter(mandatory=$true)][Test]$Test 41 | ) 42 | <# 43 | .SYNOPSIS 44 | Provides a mechanism for marking Koans in IntroTraining\AboutOOP.koans.ps1. 45 | 46 | .PARAMETER Test 47 | Specify the name of the test you want the result for. 48 | This must be of the enum type Test 49 | 50 | .INPUTS 51 | None. You cannot pipe objects through to OOPTestAnswers. 52 | 53 | .OUTPUTS 54 | System.String or System.Boolean 55 | OOPTestAnswers will output a System.String for: 56 | 57 | [Test]::NorwayFjordsSchematics 58 | [Test]::Eyjafjallajokull 59 | [Test]::CoventrySchematics 60 | 61 | And a System.Boolean for: 62 | 63 | [Test]::doggoNose 64 | [Test]::doggoBark 65 | [Test]::doggoWoof 66 | [Test]::doggoTail 67 | [Test]::phoneVibrating 68 | [Test]::keyboardColour 69 | [Test]::lightPower 70 | [Test]::toast 71 | 72 | 73 | .EXAMPLE 74 | PS> OOPTestAnswers -Test doggoNose 75 | 76 | .LINK 77 | https://github.com/Sudoblark/PSKoans 78 | 79 | .LINK 80 | https://github.com/vexx32/PSKoans 81 | #> 82 | return $ResultsHash[$Test] 83 | } 84 | -------------------------------------------------------------------------------- /drafts/ExtendedMeasures.ps1: -------------------------------------------------------------------------------- 1 | $String = ( 2 | 'RwBlAHQALQBDAGgAaQBsAGQASQB0AGUAbQAgAC0AUgBlAGMAdQByAHMAZQAgAC0ARgBvAHIAYwBl' + 3 | 'ACAALQBGAGkAbABlACAALQBFAHIAcgBvAHIAQQBjAHQAaQBvAG4AIABJAGcAbgBvAHIAZQAgAHwA' + 4 | 'IABHAHIAbwB1AHAALQBPAGIAagBlAGMAdAAgAEUAeAB0AGUAbgBzAGkAbwBuACAAfAAgAEYAbwBy' + 5 | 'AEUAYQBjAGgALQBPAGIAagBlAGMAdAAgAHsAIABBAGQAZAAtAE0AZQBtAGIAZQByACAALQBJAG4A' + 6 | 'cAB1AHQATwBiAGoAZQBjAHQAIAAkAF8AIAAtAE0AZQBtAGIAZQByAFQAeQBwAGUAIABOAG8AdABl' + 7 | 'AFAAcgBvAHAAZQByAHQAeQAgAC0ATgBhAG0AZQAgACIAVABvAHQAYQBsAEwAZQBuAGcAdABoACIA' + 8 | 'IAAtAFYAYQBsAHUAZQAgACgAJABfAC4ARwByAG8AdQBwACAAfAAgAE0AZQBhAHMAdQByAGUALQBP' + 9 | 'AGIAagBlAGMAdAAgAEwAZQBuAGcAdABoACAALQBTAHUAbQApAC4AUwB1AG0AIAAtAFAAYQBzAHMA' + 10 | 'VABoAHIAdQAgAH0AIAB8ACAAUwBvAHIAdAAtAE8AYgBqAGUAYwB0ACAAVABvAHQAYQBsAEwAZQBu' + 11 | 'AGcAdABoACAALQBEAGUAcwBjAGUAbgBkAGkAbgBnACAAfAAgAFMAZQBsAGUAYwB0AC0ATwBiAGoA' + 12 | 'ZQBjAHQAIAAtAEYAaQByAHMAdAAgADUAIAB8ACAARgBvAHIARQBhAGMAaAAtAE8AYgBqAGUAYwB0' + 13 | 'ACAAewAgACQAXwAuAEcAcgBvAHUAcAAgAHwAIABTAG8AcgB0AC0ATwBiAGoAZQBjAHQAIABMAGUA' + 14 | 'bgBnAHQAaAAgAC0ARABlAHMAYwBlAG4AZABpAG4AZwAgAHwAIABTAGUAbABlAGMAdAAtAE8AYgBq' + 15 | 'AGUAYwB0ACAALQBGAGkAcgBzAHQAIAA2ACAAfQAgAHwAIABTAG8AcgB0AC0ATwBiAGoAZQBjAHQA' + 16 | 'IAB7ACAAJABfAC4ATABhAHMAdABBAGMAYwBlAHMAcwBUAGkAbQBlAC4ATQBvAG4AdABoACAAfQA=' 17 | ) 18 | 19 | $Solution = { 20 | Get-ChildItem -Recurse -Force -File -ErrorAction Ignore | 21 | Group-Object Extension | Add-Member -MemberType NoteProperty -Name "TotalLength" -Value ( 22 | $_.Group | Measure-Object Length -Sum 23 | ).Sum -PassThru | 24 | Sort-Object TotalLength -Descending | 25 | Select-Object -First 5 | 26 | ForEach-Object { 27 | $_.Group | 28 | Sort-Object Length -Descending | 29 | Select-Object -First 6 30 | } | Sort-Object { $_.LastAccessTime.Month } 31 | } 32 | $Solution = [scriptblock]::Create( 33 | [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($String)) 34 | ).InvokeReturnAsIs() 35 | -------------------------------------------------------------------------------- /formatting/PSKoans.CompleteResult.format.ps1: -------------------------------------------------------------------------------- 1 | Write-FormatView -AsList -TypeName PSKoans.CompleteResult -Name List -Property KoansPassed, TotalKoans, RequestedTopic, Complete 2 | 3 | Write-FormatView -TypeName PSKoans.CompleteResult -Action { 4 | Write-FormatViewExpression -If { $_.RequestedTopic } -ControlName Prompt.TopicList -ScriptBlock { 5 | @{ 6 | FormatString = @" 7 | Congratulations! You have taken the first steps towards enlightenment. 8 | 9 | You have completed the {0}: 10 | {1} 11 | "@ 12 | Topics = $_.RequestedTopic 13 | } 14 | } 15 | 16 | Write-FormatViewExpression -If { -not $_.RequestedTopic } -Text @" 17 | Congratulations! You have made great progress towards enlightenment. 18 | 19 | The journey to mastery is never-ending; if at any point you feel the need to 20 | revisit a topic, you can simply call "Reset-PSKoan -Topic `$name" to reset 21 | that specific topic and work through it once again. 22 | 23 | May your newfound knowledge serve you well, and may you find myriad ways to 24 | pass it along to others in turn. 25 | 26 | Mountains are, once again, merely mountains. 27 | "@ -ForegroundColor 'PSKoans.Meditation.Text' 28 | 29 | Write-FormatViewExpression -Newline 30 | Write-FormatViewExpression -Newline 31 | 32 | Write-FormatViewExpression -ScriptBlock { 1 } -ControlName Prompt.Koan 33 | 34 | Write-FormatViewExpression -ScriptBlock { $_ } -ControlName Prompt.ProgressPreamble 35 | 36 | Write-FormatViewExpression -If { $_.RequestedTopic.Count -gt 1 } -ControlName Prompt.ProgressBar -ScriptBlock { 37 | @{ 38 | Completed = $_.CurrentTopic.Completed 39 | Total = $_.CurrentTopic.Total 40 | Name = $_.CurrentTopic.Name 41 | Width = 0.5 42 | } 43 | } 44 | 45 | Write-FormatViewExpression -If { $_.RequestedTopic.Count -eq 1 } -ControlName Prompt.ProgressBar -ScriptBlock { 46 | @{ 47 | Completed = $_.CurrentTopic.Completed 48 | Total = $_.CurrentTopic.Total 49 | Name = $_.CurrentTopic.Name 50 | Width = 0.8 51 | } 52 | } 53 | 54 | Write-FormatViewExpression -If { 55 | -not $_.RequestedTopic -or 56 | $_.RequestedTopic.Count -gt 1 57 | } -ControlName Prompt.ProgressBar -ScriptBlock { 58 | @{ 59 | Completed = $_.KoansPassed 60 | Total = $_.TotalKoans 61 | Name = 'Total' 62 | Width = 0.8 63 | } 64 | } 65 | 66 | Write-FormatViewExpression -NewLine 67 | Write-FormatViewExpression -NewLine 68 | 69 | Write-FormatViewExpression -If { -not $_.RequestedTopic } -Text @" 70 | If you would like to further your studies in this manner, consider investing in 71 | "PowerShell by Mistake" by Don Jones - https://leanpub.com/powershell-by-mistake 72 | "@ -ForegroundColor 'PSKoans.Meditation.Text' 73 | } 74 | -------------------------------------------------------------------------------- /formatting/PSKoans.KoanInfo.format.ps1: -------------------------------------------------------------------------------- 1 | Write-FormatView -TypeName PSKoans.KoanInfo -Property Topic, Module, Position 2 | -------------------------------------------------------------------------------- /images/Show-Karma_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/images/Show-Karma_1.png -------------------------------------------------------------------------------- /images/Show-Karma_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/images/Show-Karma_2.png -------------------------------------------------------------------------------- /images/logo-128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/images/logo-128px.png -------------------------------------------------------------------------------- /images/logo-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/images/logo-64px.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexx32/PSKoans/1317701a7d2bf6ca7efff0eb21d3a3d0478740e9/images/logo.png -------------------------------------------------------------------------------- /templates/environment-setup.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: PowerShell@2 3 | displayName: 'Install Dependencies' 4 | 5 | inputs: 6 | targetType: 'inline' 7 | script: | 8 | $Params = @{ 9 | Scope = 'CurrentUser' 10 | Force = $true 11 | } 12 | 13 | Write-Host "Installing Modules" 14 | $Params.Name = @( 'PSDeploy', 'BuildHelpers', 'PlatyPS' ) 15 | $Params | Out-String | Write-Host 16 | Install-Module @Params 17 | 18 | $Params.Name = 'Pester' 19 | $Params.SkipPublisherCheck = $true 20 | $Params.MinimumVersion = '5.0.2' 21 | $Params | Out-String | Write-Host 22 | Install-Module @Params 23 | $Params.Remove('SkipPublisherCheck') 24 | $Params.Remove('MinimumVersion') 25 | 26 | $Params.Name = 'EZOut' 27 | $Params.AllowClobber = $true 28 | $Params | Out-String | Write-Host 29 | Install-Module @Params 30 | 31 | errorActionPreference: 'stop' 32 | failOnStderr: true 33 | pwsh: true 34 | 35 | - task: PowerShell@2 36 | displayName: 'Initialize Environment' 37 | 38 | inputs: 39 | targetType: 'filePath' 40 | filePath: ./Build/Initialize-Environment.ps1 41 | 42 | errorActionPreference: 'stop' 43 | failOnStdErr: true 44 | pwsh: true 45 | -------------------------------------------------------------------------------- /templates/install-built-module.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: repositoryPath 3 | type: string 4 | default: '$(System.DefaultWorkingDirectory)' 5 | - name: repositoryName 6 | type: string 7 | default: 'FileSystem' 8 | - name: artifactName 9 | type: string 10 | default: 'PSKoans.nupkg' 11 | 12 | steps: 13 | - template: ./register-local-repo.yml 14 | parameters: 15 | repositoryPath: ${{ parameters.repositoryPath }} 16 | repositoryName: ${{ parameters.repositoryName }} 17 | 18 | - task: DownloadPipelineArtifact@2 19 | displayName: 'Download Built Module Artifact' 20 | inputs: 21 | artifact: ${{ parameters.artifactName }} 22 | path: ${{ parameters.repositoryPath }} 23 | 24 | - task: PowerShell@2 25 | displayName: 'Install PSKoans from Nupkg' 26 | inputs: 27 | targetType: 'inline' 28 | script: | 29 | $pesterParams = @{ 30 | Name = 'Pester' 31 | MinimumVersion = '5.0.2' 32 | ProviderName = 'NuGet' 33 | Path = '${{ parameters.repositoryPath }}' 34 | Force = $true 35 | Source = 'PSGallery' 36 | } 37 | 38 | Register-PackageSource -Name PSGallery -ProviderName NuGet -Location https://www.powershellgallery.com/api/v2 -Force 39 | Save-Package @pesterParams | Select-Object -Property Name, Version, Status, Source 40 | Install-Module PSKoans -Repository ${{ parameters.repositoryName }} -Force -Scope CurrentUser 41 | 42 | errorActionPreference: 'stop' 43 | failOnStderr: true 44 | pwsh: true 45 | -------------------------------------------------------------------------------- /templates/register-local-repo.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: repositoryPath 3 | type: string 4 | default: '$(System.DefaultWorkingDirectory)' 5 | - name: repositoryName 6 | type: string 7 | default: 'FileSystem' 8 | 9 | steps: 10 | - task: PowerShell@2 11 | displayName: 'Register FileSystem Repository' 12 | inputs: 13 | targetType: 'filePath' 14 | filePath: ./Build/Register-FileSystemRepository.ps1 15 | arguments: -Path '${{ parameters.repositoryPath }}' -Name '${{ parameters.repositoryName }}' 16 | 17 | errorActionPreference: 'stop' 18 | failOnStderr: true 19 | pwsh: true 20 | -------------------------------------------------------------------------------- /templates/test-steps.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: PowerShell@2 3 | displayName: 'Run Pester Tests' 4 | 5 | inputs: 6 | targetType: 'filePath' 7 | filePath: ./Build/Invoke-ModuleTests.ps1 8 | 9 | errorActionPreference: 'stop' 10 | failOnStderr: true 11 | pwsh: true 12 | 13 | - task: PublishTestResults@2 14 | displayName: 'Publish Test Results' 15 | condition: succeededOrFailed() 16 | 17 | inputs: 18 | testResultsFormat: NUnit 19 | testResultsFiles: '$(TestResults)' 20 | searchFolder: '$(Build.ArtifactStagingDirectory)' 21 | mergeTestResults: true 22 | 23 | - task: PublishCodeCoverageResults@1 24 | displayName: 'Publish Code Coverage' 25 | condition: succeededOrFailed() 26 | 27 | inputs: 28 | codeCoverageTool: JaCoCo 29 | summaryFileLocation: '$(Build.ArtifactStagingDirectory)/$(CodeCoverageFile)' 30 | #reportDirectory: '$(Build.ArtifactStagingDirectory)' 31 | pathToSources: '$(SourceFolders)' 32 | --------------------------------------------------------------------------------