PSAnsibleCmdline.psm1
using namespace System.Management.Automation #region Get-PlaybookParams.ps1 function ToTitleCase { param ( [Parameter(Mandatory, Position = 0, ValueFromPipeline)] [string]$String ) begin {$Culture = Get-Culture} process { $Culture.TextInfo.ToTitleCase($String) -replace '-' } } function Get-PlaybookParams { [Diagnostics.CodeAnalysis.SuppressMessage("PSUseSingularNouns", "")] [OutputType([RuntimeDefinedParameterDictionary])] [CmdletBinding()] param () if ($Script:PlaybookParams) {return $Script:PlaybookParams} $Help = ansible-playbook --help $Help = $Help.Where({$_ -match '^options:'}, 'SkipUntil') -ne "" -notmatch '^( )?\w' | Out-String $Blocks = $Help.Trim() -split "`n (?=-)" [string[]]$Aliases = $null $SingleLetterAliases = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $Params = $Blocks | ForEach-Object { $Aliases, $Help = $_ -split "(?s)\s{2,}" $Aliases = $Aliases -split ", " $Help = $Help -join " " if ($Aliases -match '-verbose') {return} # special case! $Arg = "" $Aliases = $Aliases | ForEach-Object {$Alias, $Arg = $_ -split " "; $Alias} # Assumption: there's no more than one single-letter alias $SingleLetterAlias = @($Aliases) -match '^-\w$' -replace '-' if ($SingleLetterAlias -and -not $SingleLetterAliases.Add($SingleLetterAlias)) { # we have a case-insensitive duplicate :-( $Aliases = @($Aliases) -notmatch '^-\w$' } $Name = @($Aliases) -match '^--' | Select-Object -First 1 $Aliases = @($Aliases) -ne $Name if ($Name -match '\w-\w') { $Aliases = $Name, $Aliases | Write-Output } $Name = ($Name | ToTitleCase) -replace '-' $Aliases = @($Aliases) -replace '^--?' $Type = if (-not $Arg) { [switch] } elseif ($Arg -match 'S$' -or $Help -match 'may be specified multiple times') { [string[]] } else { [string] } $Attrs = [Collections.ObjectModel.Collection[Attribute]]::new() if ($Aliases) { $Attrs.Add([Alias]::new($Aliases)) } $ParamAttr = [ParameterAttribute]::new() $ParamAttr.HelpMessage = $Help $Attrs.Add($ParamAttr) [RuntimeDefinedParameter]::new($Name, $Type, $Attrs) } $Script:PlaybookParams = [RuntimeDefinedParameterDictionary]::new() $Params | ForEach-Object {$Script:PlaybookParams.Add($_.Name, $_)} $Script:PlaybookParams } #endregion Get-PlaybookParams.ps1 #region play-book.ps1 function Invoke-AnsiblePlaybook { [CmdletBinding()] param ( [Parameter(Mandatory, Position = 0)] [string]$Playbook, [Alias('vv', 'vvv', 'vvvv', 'vvvvv')] [switch]$v ) dynamicparam { $DynParams = Get-PlaybookParams $DynParams } end { $Verbosity = if ($v -or $VerbosePreference -notin ('SilentlyContinue', 'Ignore')) { if ($MyInvocation.Line -match '\s-(v*)(\s|$)') {$Matches[1].Length} else {3} } $PlaybookArgs = [Collections.Generic.List[string]]::new() $PSBoundParameters.GetEnumerator() | ForEach-Object { $Param = $DynParams[$_.Key] if (-not $Param) {return} # filter out static and common params $PSName = $_.Key $Aliases = $Param.Attributes.AliasNames $Name = $Aliases | Where-Object {$_ -replace '-' -ilike $PSName} | Select-Object -First 1 if (-not $Name) {$Name = $PSName.ToLower()} if ($Param.ParameterType -eq [switch]) { $PlaybookArgs.Add("--$Name") } else { $PlaybookArgs.Add("--$Name") $PlaybookArgs.Add("$($_.Value)") } } # If the user passed a value that's not in the completion cache, invalidate the cache $PSBoundParameters.GetEnumerator() | ForEach-Object { $CompletionKey = $Script:PlaybookCompletableParams[$_.Key] if (-not $CompletionKey) {return} $Completions = $Script:PlaybookCompletionValues[$CompletionKey] if ($_.Value | Where-Object {$_ -inotin $Completions}) { $Script:PlaybookCompletionValues[$CompletionKey] = $null } } if ($Verbosity) { $PlaybookArgs.Add("-$('v' * $Verbosity)") } try { $ANSIBLE_DEBUG = $env:ANSIBLE_DEBUG if ($DebugPreference -notin ('SilentlyContinue', 'Ignore')) {$env:ANSIBLE_DEBUG = 1} if ($Verbosity) {$VerbosePreference = 'Continue'} Write-Verbose "ansible-playbook $PlaybookArgs $Playbook" ansible-playbook $PlaybookArgs $Playbook } finally { $env:ANSIBLE_DEBUG = $ANSIBLE_DEBUG } } } Set-Alias play-book Invoke-AnsiblePlaybook $Script:PlaybookCompletionValues = @{} $Script:PlaybookCompletableParams = @{ Tags = 'Tags' SkipTags = 'Tags' StartAtTask = 'Tasks' Limit = 'Hosts' } $Script:PlaybookCompleters = @{ Tags = { param ($Playbook, $Inventory) $PSBoundParameters.ListTags = $true $Output = Invoke-Playbook @PSBoundParameters $Tags = $Output | ForEach-Object {if ($_ -match 'TAGS: \[(?<tags>.+?)\]') {$Matches.tags -split ', '}} | Sort-Object -Unique $Tags, 'always', 'never' | Write-Output } Tasks = { param ($Playbook, $Inventory, $Tags, $SkipTags) $PSBoundParameters.ListTasks = $true $Output = Invoke-Playbook @PSBoundParameters $Tasks = $Output | ForEach-Object {if ($_ -match '^\s+[^:]+ : (?<task>.*?)\s+TAGS:') {$Matches.task}} | Select-Object -Unique $Tasks } Hosts = { param ($Playbook, $Inventory) $PSBoundParameters.ListHosts = $true $Output = Invoke-Playbook @PSBoundParameters $Hosts = $Output | ForEach-Object {if ($_ -match '^\s+(?<host>\S*[^:])$') {$Matches.host}} | Sort-Object -Unique $Hosts, 'localhost', 'all' | Write-Output } } $PlaybookCompletableParams.GetEnumerator() | ForEach-Object { $ParamName = $_.Key $CompletionKey = $_.Value $Fetcher = $PlaybookCompleters[$CompletionKey] $Completer = { param ($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Completions = $Script:PlaybookCompletionValues[$CompletionKey] $wordToComplete = $wordToComplete -replace "^(?<quote>['`"])(?<content>.*?)\k<quote>$", '${content}' # The user may be developing the playbook and updating tags, tasks, or hosts. # If they type a word to complete that we don't have, invalidate the cache. foreach ($Attempt in 1..2) { $ShouldRetry = $true if (-not $Completions) { $Completions = & $Fetcher @fakeBoundParameters $Script:PlaybookCompletionValues[$CompletionKey] = $Completions $ShouldRetry = $false } $Completions = (@($Completions) -like "$wordToComplete*"), (@($Completions) -like "*$wordToComplete*") | Write-Output if ($Completions -and -not $ShouldRetry) {break} } @($Completions) -replace '.* .*', "'`$0'" }.GetNewClosure() Register-ArgumentCompleter -CommandName Invoke-Playbook -ParameterName $ParamName -ScriptBlock $Completer } #endregion play-book.ps1 |