Commands/Protocols/OpenAPI-Protocol.ps.ps1
Protocol function OpenAPI { <# .SYNOPSIS OpenAPI protocol .DESCRIPTION Converts an OpenAPI to PowerShell. This protocol will generate a PowerShell script to communicate with an OpenAPI. .EXAMPLE # We can easily create a command that talks to a single REST api described in OpenAPI. Import-PipeScript { function Get-GitHubIssue { [OpenAPI(SchemaURI= 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json#/repos/{owner}/{repo}/issues/get')] param() } } Get-GitHubIssue -Owner StartAutomating -Repo PipeScript .EXAMPLE # We can also make a general purpose command that talks to every endpoint in a REST api. Import-PipeScript { function GitHubApi { [OpenAPI(SchemaURI='https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json')] param() } } GitHubApi '/zen' .EXAMPLE # We can also use OpenAPI as a command. Just pass a URL, and get back a script block. openapi https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml#/models/get .EXAMPLE $TranspiledOpenAPI = { openapi https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml#/models/get } | Use-PipeScript & $TranspiledOpenAPI # Should -BeOfType ([ScriptBlock]) #> [Alias('OpenAPI','Swagger')] [ValidateScript({ $commandAst = $_ if ($commandAst -isnot [Management.Automation.Language.CommandAst]) { return $false } if ($commandAst.CommandElements.Count -eq 1) { return $false } # If neither command element contained a URI if (-not ($commandAst.CommandElements[1].Value -match '^https{0,1}://')) { return $false } return ($commandAst.CommandElements[0].Value -in 'OpenAPI', 'swagger') })] [CmdletBinding(PositionalBinding=$false)] param( # The OpenAPI SchemaURI. [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='Protocol',Position=0)] [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='Interactive',Position=0)] [Parameter(Mandatory,ParameterSetName='ScriptBlock')] [uri] $SchemaUri, # The Command's Abstract Syntax Tree. # This is provided by PipeScript when transpiling a protocol. [Parameter(Mandatory,ParameterSetName='Protocol')] [Management.Automation.Language.CommandAST] $CommandAst, # The ScriptBlock. # This is provided when transpiling the protocol as an attribute. # Providing a value here will run this script's contents, rather than a default implementation. [Parameter(Mandatory,ParameterSetName='ScriptBlock',ValueFromPipeline)] [scriptblock] $ScriptBlock = {}, # One or more property prefixes to remove. # Properties that start with this prefix will become parameters without the prefix. [Alias('Remove Property Prefix')] [string[]] $RemovePropertyPrefix, # One or more properties to ignore. # Properties whose name or description is like this keyword will be ignored. [Alias('Ignore Property', 'IgnoreProperty','SkipProperty', 'Skip Property')] [string[]] $ExcludeProperty, # One or more properties to include. # Properties whose name or description is like this keyword will be included. [Alias('Include Property')] [string[]] $IncludeProperty, # The name of the parameter used for an access token. # By default, this will be 'AccessToken' [Alias('Access Token Parameter')] [string] $AccessTokenParameter, # The name of the parameter aliases used for an access token [Alias('Access Token Aliases')] [string[]] $AccessTokenAlias, # If set, will not mark a parameter as required, even if the schema indicates it should be. [Alias('NoMandatoryParameters','No Mandatory Parameters', 'NoMandatories', 'No Mandatories')] [switch] $NoMandatory, # If provided, will decorate any outputs from the REST api with this typename. # If this is not provied, the URL will be used as the typename. [Alias('Output Type Name', 'Decorate', 'PSTypeName', 'PsuedoTypeName','Psuedo Type', 'Psuedo Type Name')] [string] $OutputTypeName ) begin { # First we declare a few filters we will reuse. # One resolves schema definitions. # The inputObject is the schema. # The arguments are the path within the schema. filter resolvePathDefinition { $in = $_.'paths' $schemaPath = $args -replace '^#' -replace '^/' -replace '^\$defs' -split '[/\.]' -ne '' foreach ($sp in $schemaPath) { $in = $in.$sp } $in } # Another converts property names into schema parameter names. filter getSchemaParameterName { $parameterName = $_ # If we had any prefixes we wished to remove, now is the time. if ($RemovePropertyPrefix) { $parameterName = $parameterName -replace "^(?>$(@($RemovePropertyPrefix) -join '|'))" if ($property.Name -like 'Experimental.*') { $null = $null } } # Any punctuation followed by a letter should be removed and replaced with an Uppercase letter. $parameterName = [regex]::Replace($parameterName, "\p{P}(\p{L})", { param($match) $match.Groups[1].Value.ToUpper() }) -replace '\p{P}' # And we should force the first letter to be uppercase. $parameterName.Substring(0,1).ToUpper() + $parameterName.Substring(1) } filter resolveSchemaDefinition { $in = $_ $schemaPath = $args -replace '^#' -replace '^/' -split '[/\.]' -ne '' foreach ($sp in $schemaPath) { $in = $in.$sp } $in } filter PropertiesToParameters { param( [ValidateSet('Query','Body','Path')] [string] $RestParameterType, [string[]] $RequiredParameter ) $inObject = $_ if ($inObject -is [Collections.IDictionary]) { $inObject = [PSCustomObject]$inObject } # Now walk thru each property in the schema $newPipeScriptParameters = [Ordered]@{} :nextProperty foreach ($property in $inObject.psobject.properties) { # Filter out excluded properties if ($ExcludeProperty) { foreach ($exc in $ExcludeProperty) { if ($property.Name -like $exc) { continue nextProperty } } } # Filter included properties if ($IncludeProperty) { $included = foreach ($inc in $IncludeProperty) { if ($property.Name -like $inc) { $true;break } } if (-not $included) { continue nextProperty } } # Convert the property into a parameter name $parameterName = $property.Name | getSchemaParameterName $newParameterInfo = [Ordered]@{ Name=$parameterName; Attributes=@() Binding = $property.Name } if ($property.value.description) { $newParameterInfo.Description = $property.value.Description } if (-not $NoMandatory -and (($property.value.required) -or ($RequiredParameter -contains $property.Name)) ) { $newParameterInfo.Mandatory = $true } $propertyTypeInfo = if ($property.value.'$ref') { # If there was a reference, try to resolve it $referencedType = $schemaObject | resolveSchemaDefinition $property.value.'$ref' if ($referencedType) { $referencedType } } else { # If there is not a oneOf, return the value if (-not $property.value.'oneOf') { $property.value } else { # If there is oneOf, see if it's an optional null $notNullOneOf = @(foreach ($oneOf in $property.value.oneOf) { if ($oneOf.type -eq 'null') { $nullAllowed = $true continue } $oneOf }) if ($notNullOneOf.Count -eq 1) { $notNullOneOf = $notNullOneOf[0] if ($notNullOneOf.'$ref') { $referencedType = $schemaObject | resolveSchemaDefinition $notNullOneOf.'$ref' if ($referencedType) { $referencedType } } else { $notNullOneOf } } else { $notNullOneOf | Sort-Object { if ($_ -is [Collections.IDictionary]) { $_.Count } else { @($_.psobject.properties).length}} -Descending | Select-Object -First 1 } } } # If there was a single type with an enum if ($propertyTypeInfo.enum) { $validSet = @() + $propertyTypeInfo.enum if ($nullAllowed) { $validSet += '' } $newParameterInfo.ValidValues = $validSet } elseif ($propertyTypeInfo.pattern) { $validPattern = $propertyTypeInfo.pattern if ($nullAllowed) { $validPattern = "(?>^\s{0}$|$validPattern)" } $validPattern = $validPattern.Replace("'", "''") # declare a regex. $newParameterInfo.Attributes += "[ValidatePattern('$validPattern')]" } elseif ($null -ne $propertyTypeInfo.minimum -and $null -ne $propertyTypeInfo.maximum) { $newParameterInfo.Attributes += "[ValidateRange($($propertyTypeInfo.minimum),$($propertyTypeInfo.maximum))]" } # If there was a property type if ($propertyTypeInfo.type) { $newParameterInfo.type = $propertyTypeInfo.type } # We also create an alias indicating the type of parameter it is. $restfulAliasName = switch ($RestParameterType) { 'Query' { "?$($property.Name)" } 'Body' { ".$($property.Name)" } 'Path' { "/$($property.Name)" } 'Header' { "^$($property.Name)" } } if ($property.Value.default) { $newParameterInfo.Default = $property.Value.default } elseif ($propertyTypeInfo.default) { $newParameterInfo.Default = $propertyTypeInfo.default } $newParameterInfo.Alias = $restfulAliasName $parameterAttributes += "`$$parameterName" # $parameterList.insert(0, [Ordered]@{$parameterName=$newParameterInfo}) $newPipeScriptParameters[$parameterName] = $newParameterInfo } $newPipeScriptParameters } # If we have not cached the schema uris, create a collection for it. if (-not $script:CachedSchemaUris) { $script:CachedSchemaUris = @{} } $beginForEveryScript = { $pathVariable = [Regex]::new('(?<Start>\/)\{(?<Variable>\w+)\}', 'IgnoreCase,IgnorePatternWhitespace') # Next declare a script block that will replace the rest variable. $ReplacePathVariable = { param($match) if ($restPathParameters -and ($null -ne $restPathParameters[$match.Groups["Variable"].Value])) { return $match.Groups["Start"].Value + ([Web.HttpUtility]::UrlEncode( $restPathParameters[$match.Groups["Variable"].Value] )) } else { return '' } } } # Last but not least, we declare a script block with the main contents of a single request $ProcessBlockStartForAnEndpoint = { # Declare a collection for each type of RESTful parameter. $restBodyParameters = [Ordered]@{} $restQueryParameters = [Ordered]@{} $restPathParameters = [Ordered]@{} $restHeaderParameters = [Ordered]@{} } $processBlockStartForAllEndpoints = { $restBodyParameters = $BodyParameter $restQueryParameters = $QueryParameter $restPathParameters = $PathParameter $restHeaderParameters = $HeaderParameter } $processForEveryScript = { # Get my script block $myScriptBlock = $MyInvocation.MyCommand.ScriptBlock $function:MySelf = $myScriptBlock $myCmdInfo = $executionContext.SessionState.InvokeCommand.GetCommand('Myself','Function') # Next, we'll use our own command's metadata to walk thru the parameters. foreach ($param in ([Management.Automation.CommandMetaData]$myCmdInfo).Parameters.GetEnumerator()) { # First get the parameter name $paramVariableName = $param.Key # Everything else we need is in the value. $param = $param.Value $restParameterType = 'Body' # if there are not attributes, this parameter will not be mapped. if (-not $param.Attributes) { continue } $specialAlias = @($param.Attributes.AliasNames) -match '^(?<t>[\./\?\^])(?<n>)' # Not walk over each parameter attribute: foreach ($attr in $param.Attributes) { # If the attribute is an Alias attribute, see if any of it's names start with a special character. # This will indicate how the parameter if ($specialAlias) { $restParameterType = switch ("$specialAlias"[0]) { '/' { 'Path' } '?' { 'Query' } '.' { 'Body' } '^' { 'Header' } } } # If the attribute is not a defaultbindingproperty attribute, if ($attr -isnot [ComponentModel.DefaultBindingPropertyAttribute]) { continue # keep moving. } $bindParameterValue = $true # Otherwise, update our object with the value $propName = $($attr.Name) $restParameterDictionary = switch ($restParameterType) { Path { $restPathParameters } Query { $restQueryParameters } Header { $restHeaderParameters } default { $restBodyParameters } } } # If our bound parameters contains the parameter if ($bindParameterValue -and $PSBoundParameters.ContainsKey($paramVariableName)) { # use that value $restParameterDictionary[$propName] = $PSBoundParameters[$paramVariableName] # (don't forget to turn switches into booleans). if ($restParameterDictionary[$propName] -is [switch]) { $restParameterDictionary[$propName] = $restParameterDictionary[$propName] -as [bool] } } else { # If we didn't pass a value, but it's a header if ($restParameterDictionary -eq $restHeaderParameters) { # check for an environment variable with that name. $environmentVariable = $ExecutionContext.SessionState.PSVariable.Get("env:$paramVariableName").Value # If there is one, use it. if ($environmentVariable) { $restParameterDictionary[$propName] = $environmentVariable } } } } if ($PSBoundParameters['Path']) { # Replace any variables in the URI Write-Verbose "Adding $Path to $BaseUri" $uri = "${BaseUri}${Path}" } # Replace any variables in the URI Write-Verbose "Replacing REST Path Parameters in: $uri" $replacedUri = $pathVariable.Replace("$Uri", $ReplacePathVariable) # If these is no replace URI, return. return if -not $replacedUri # Write the replaced URI to verbose Write-Verbose "$replacedUri" # then add query parameters $queryString = if ($restQueryParameters.Count) { "?" + (@( foreach ($qp in $restQueryParameters.GetEnumerator()) { # Query parameters with multiple values are repeated if ($qp.Value -is [Array]) { foreach ($queryValue in $qp.Value) { "$($qp.Key)=$([Web.HttpUtility]::UrlEncode("$($queryValue)"))" } } else { "$($qp.Key)=$([Web.HttpUtility]::UrlEncode("$($qp.Value)"))" } } ) -join '&') } else {''} # If any were added if ($queryString) { # append the query string $replacedUri += $queryString # and write it verbose. Write-Verbose "Adding Query String: $queryString" } # Default the method to get, if one has not been provided if (-not $Method) { $Method = 'get' } # Create a cache for authorization headers, if one does not exist if (-not $script:CachedAuthorizationHeaders) { $script:CachedAuthorizationHeaders = @{} } if ($restHeaderParameters.Count) { foreach ($kv in @($restHeaderParameters.GetEnumerator())) { if ($kv.Key -notlike '*:*') { continue } $headerName, $headerPrefix = $kv.Key -split ':', 2 $restHeaderParameters[$headerName] = "$headerPrefix $($kv.Value)" # If the header was 'Authorization' if ($headerName -eq 'Authorization') { # cache the value for this BaseURI $script:CachedAuthorizationHeaders["$BaseUri"] = $restHeaderParameters[$headerName] } $restHeaderParameters.Remove($kv.Key) } } # If we have a cached authorization header and didn't provide one, use it. if ($script:CachedAuthorizationHeaders["$BaseUri"] -and -not $restHeaderParameters['Authorization']) { $restHeaderParameters['Authorization'] = $script:CachedAuthorizationHeaders["$BaseUri"] } $invokeSplat = @{ Uri = $replacedUri Method = $method Headers = $restHeaderParameters } if ($restBodyParameters.Count) { $requestBody = ConvertTo-Json $restBodyParameters -Depth 100 $invokeSplat.ContentType = 'application/json' $invokeSplat.Body = $requestBody } if (-not $OutputTypeName) { $OutputTypeName = "$uri" } $restfulOutput = Invoke-RestMethod @invokeSplat foreach ($restOut in $restfulOutput) { if ($restOut -isnot [Byte] -and $restOut -isnot [Byte[]] -and $restOut.pstypenames) { if ($restOut.pstypenames -notcontains $OutputTypeName) { $restOut.pstypenames.insert(0, $OutputTypeName) } $restOut } else { $restOut } } } $myCmd = $MyInvocation.MyCommand } process { # If we are being invoked as a protocol if ($PSCmdlet.ParameterSetName -eq 'Protocol') { # we will parse our input as a sentence. $mySentence = $commandAst.AsSentence($MyInvocation.MyCommand) # Walk thru all mapped parameters in the sentence $myParams = [Ordered]@{} + $PSBoundParameters foreach ($paramName in $mySentence.Parameters.Keys) { if (-not $myParams.Contains($paramName)) { # If the parameter was not directly supplied $myParams[$paramName] = $mySentence.Parameters[$paramName] # grab it from the sentence. foreach ($myParam in $myCmd.Parameters.Values) { if ($myParam.Aliases -contains $paramName) { # set any variables that share the name of an alias $ExecutionContext.SessionState.PSVariable.Set($myParam.Name, $mySentence.Parameters[$paramName]) } } # and set this variable for this value. $ExecutionContext.SessionState.PSVariable.Set($paramName, $mySentence.Parameters[$paramName]) } } } if (-not $SchemaUri.Scheme) { $SchemaUri = [uri]"https://$($SchemaUri.OriginalString -replace '://')" } # We will try to cache the schema URI at a given scope. $script:CachedSchemaUris[$SchemaUri] = $schemaObject = if (-not $script:CachedSchemaUris[$SchemaUri]) { Invoke-RestMethod -Uri $SchemaUri } else { $script:CachedSchemaUris[$SchemaUri] } if ($schemaObject -is [string] -and $SchemaUri -like '*y*ml*') { $requirePSYaml = Invoke-PipeScript { require latest powershell-yaml } $schemaObject = $schemaObject | ConvertFrom-Yaml -Ordered } # If we do not have a schema object, error out. if (-not $schemaObject) { Write-Error "Could not get Schema from '$schemaUri'" return } # If the object does not have .paths, it's not an OpenAPI schema, error out. if (-not $schemaObject.paths) { Write-Error "'$schemaUri' does not have .paths" return } # Resolve the schema paths(s) we want to generate. $UrlRelativePath = '' $RestMethod = 'get' $schemaDefinition = if ($SchemaUri.Fragment) { $pathFragment = [Web.HttpUtility]::UrlDecode($SchemaUri.Fragment -replace '^\#') $methodFragment = @($SchemaUri.Fragment -split '/')[-1] if ($schemaObject.paths.$pathFragment) { $schemaObject.paths.$pathFragment $UrlRelativePath = $pathFragment } elseif ($( $pathAndMethod = $schemaObject.paths.( $pathFragment -replace "/$MethodFragment$" ).$methodFragment $pathAndMethod )) { $UrlRelativePath = ($pathFragment -replace "/$MethodFragment$") $RestMethod = $methodFragment $pathAndMethod } elseif ($( $resolvedSchemaFragment = $schemaObject | resolveSchemaDefinition $SchemaUri.Fragment $resolvedSchemaFragment )) { $resolvedSchemaFragment } else { Write-Error "Could not resolve $($schemaUri.Fragment) within $($schemaUri)" return } } else { $null } # Start off by carrying over the summary. $newPipeScriptSplat = @{ Synopsis = $schemaDefinition.summary } if ($schemaDefinition.properties -is [Collections.IDictionary]) { $schemaDefinition.properties = [PSCustomObject]$schemaDefinition.properties } $parametersFromProperties = $schemaDefinition.properties | PropertiesToParameters # Now walk thru each property in the schema $newPipeScriptParameters = [Ordered]@{} + $parametersFromProperties if ($schemaDefinition.parameters) { foreach ($pathOrQueryParameter in $schemaDefinition.parameters) { if ($pathOrQueryParameter.'$ref') { $pathOrQueryParameter = $schemaObject | resolveSchemaDefinition $pathOrQueryParameter.'$ref' } if (-not $pathOrQueryParameter.Name) { continue } $newPipeScriptParameters[$pathOrQueryParameter.Name] = @{ Name = $pathOrQueryParameter.Name | getSchemaParameterName Description = $pathOrQueryParameter.description Attribute = if ($pathOrQueryParameter.required) { "Mandatory"} else { ''} Type = $pathOrQueryParameter.schema.type Alias = "$( switch ($pathOrQueryParameter.in) { Path {'/'} Query {'?'} Header {'^'} default {'.'}} )$($pathOrQueryParameter.Name)" Binding = "$($pathOrQueryParameter.Name)" } } if ($schemaDefinition.parameters -is [Collections.IDictionary]) { $schemaDefinition.parameters = [PSCustomObject]$schemaDefinition.parameters } } if ($schemaDefinition.requestBody) { $bodySchema = $schemaDefinition.requestBody.content.'application/json'.schema $parametersFromBody = if ($bodySchema.'$ref') { $resolvedRef = $schemaObject | resolveSchemaDefinition $bodySchema.'$ref' $resolvedRef.properties | PropertiesToParameters -RestParameterType Body -RequiredParameter $resolvedRef.required } elseif ($bodySchema.properties) { $bodySchema.properties | PropertiesToParameters -RestParameterType Body } else { @{} } try { $newPipeScriptParameters += $parametersFromBody } catch { foreach ($kv in $newPipeScriptParameters.GetEnumerator()) { if ($newPipeScriptParameters[$kv.Key]) { # Conflict between body and query. IMHO this is bad api design on their part. $null = $null } else { $newPipeScriptParameters[$kv.Key] = $kv.Value } } } } # If we did not have a specific Schema Defined by the path. if (-not $SchemaDefinition) { if ($schemaObject.paths -is [Collections.IDictionary]) { $schemaObject.paths = [PSCustomObject]$schemaObject.paths } $validPaths = @( foreach ($pathProperty in $schemaObject.paths.psobject.properties) { $pathProperty.name } ) # we will make a generic command that can invoke any path. $newPipeScriptParameters['Path'] = [Ordered]@{ Name = 'Path' Aliases = 'RelativePath' Help = 'The relative path of the RESTful endpoint' Attributes = 'ValueFromPipelineByPropertyName' Type = [string[]] ValidValues = $validPaths } $newPipeScriptParameters['BodyParameter'] = [Ordered]@{ Aliases = 'BodyParameters', 'Body' Help = 'Parameters passed in the body of the request' Attributes = 'ValueFromPipelineByPropertyName' Type = [Collections.IDictionary] DefaultValue = {[Ordered]@{}} } $newPipeScriptParameters['QueryParameter'] = [Ordered]@{ Aliases = 'QueryParameters', 'Query' Help = 'Parameters passed in the query string of the request' Attributes = 'ValueFromPipelineByPropertyName' Type = [Collections.IDictionary] DefaultValue = {[Ordered]@{}} } $newPipeScriptParameters['PathParameter'] = [Ordered]@{ Aliases = 'PathParameters' Help = 'Parameters passed in the path of the request' Attributes = 'ValueFromPipelineByPropertyName' Type = [Collections.IDictionary] DefaultValue = {[Ordered]@{}} } $newPipeScriptParameters['HeaderParameter'] = [Ordered]@{ Aliases = 'HeaderParameters', 'Header','Headers' Help = 'Parameters passed in the headers of the request' Attributes = 'ValueFromPipelineByPropertyName' Type = [Collections.IDictionary] DefaultValue = {[Ordered]@{}} } $newPipeScriptParameters['Method'] = [Ordered]@{ Name = 'Method' Aliases = 'HTTPMethod' Help = 'The HTTP Method used for the request' Attributes = 'ValueFromPipelineByPropertyName' Type = [string] } } if (-not $AccessTokenParameter) { $AccessTokenParameter = 'AccessToken' } if (-not $newPipeScriptParameters[$AccessTokenParameter]) { if (-not $AccessTokenAlias) { $AccessTokenAlias = 'PersonalAccessToken', 'BearerToken', '^Bearer' } elseif ($AccessTokenAlias -notlike '^*') { $AccessTokenAlias += '^Bearer' } $newPipeScriptParameters[$AccessTokenParameter] = @{ Aliases = 'PersonalAccessToken', 'BearerToken', '^Bearer' Binding = 'Authorization:Bearer' Type = 'string' Description = 'The Access Token used for the request.' } } $newPipeScriptSplat.Parameter = $newPipeScriptParameters # If there was no scriptblock, or it was nothing but an empty param() if ($ScriptBlock.IsEmpty -or $ScriptBlock -match '^[\s\r\n]{0,}(?:param\(\))?[\s\r\n]{0,}$') { $absoluteUri = "$($schemaObject.servers.url)" $newPipeScriptSplat.Begin = [ScriptBlock]::create( @( "`$Method, `$Uri, `$outputTypeName = '$($RestMethod)', '${absoluteUri}${UrlRelativePath}', '$OutputTypeName'" "`$BaseUri = `$Uri" $beginForEveryScript ) -join [Environment]::NewLine ) if ($schemaDefinition) { $newPipeScriptSplat.Process = [ScriptBlock]::Create( @( $ProcessBlockStartForAnEndpoint $processForEveryScript ) -join [Environment]::NewLine ) } else { $newPipeScriptSplat.Process = [ScriptBlock]::Create( @( $ProcessBlockStartForAllEndpoints $processForEveryScript ) -join [Environment]::NewLine ) } } # If we are transpiling a script block if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock') { # join the existing script with the schema information Join-PipeScript -ScriptBlock $ScriptBlock, ( New-PipeScript @newPipeScriptSplat ) } elseif ($PSCmdlet.ParameterSetName -eq 'Protocol') { # Otherwise, create a script block that produces the schema. [ScriptBlock]::create("{ $(New-PipeScript @newPipeScriptSplat) }") } else { New-PipeScript @newPipeScriptSplat } } } |