Curl2PS.psm1
Class Curl2PSParameterDefinition { [string]$Type [string]$ParameterName [PSObject]$Value } Class Curl2PSParameterTransformer { [string]$Description [version]$MinimumVersion [string]$ParameterName [string]$Type [scriptblock]$Value [string]$Warning [Curl2PSParameterTransformer]$AdditionalParameters Curl2PSArgumentDefinition() {} } Function ConvertTo-Curl2PSParameter { [OutputType([Curl2PSParameterDefinition])] param ( [string]$ParamValue, [string]$ParamName ) if ($config.ParameterTransformers.Keys -ccontains $paramName) { # if the argument value is a string, locate the correct argument in the config $ogParamName = $paramName if ($config.ParameterTransformers[$paramName] -is [string]) { $paramName = $config.ParameterTransformers[$paramName] } # if the argument is an array, get the one with highest met minimum version if ($config.ParameterTransformers[$paramName] -is [array]) { $argConfig = $null foreach ($argument in $config.ParameterTransformers[$paramName]) { if ($null -eq $argConfig -and -not $argument.MinimumVersion) { $argConfig = $argument } if ($argument.MinimumVersion -and [version]$argument.MinimumVersion -lt $PSVersionTable.PSVersion -and $argument.MinimumVersion -gt $argConfig.MinimumVersion) { $argConfig = $argument } } } else { $argConfig = $config.ParameterTransformers[$paramName] } # minimum version check (i.e. SkipCertificateCheck) if ($argConfig.MinimumVersion -and [version]$argConfig.MinimumVersion -gt $PSVersionTable.PSVersion) { Write-Warning "The parameter $ogParamName is not supported in this version of PowerShell. Minimum version required: $($argConfig.MinimumVersion)" continue } # invoke the config's script block to return the value $out = Invoke-Command -ScriptBlock $argConfig.Value -ArgumentList $paramValue $data = [Curl2PSParameterDefinition]@{ Type = $argConfig.Type ParameterName = $argConfig.ParameterName Value = $out } # headers are a special case, as they are a hashtable of key/value pairs and sometimes represent other parameters for Invoke-RestMethod if ($data.ParameterName -eq 'Headers' -and $config.Headers.Keys -contains $data.Value.Keys[0]) { $key = $data.Value.Keys[0] if ($config.Headers[$key].Keys -notcontains 'MinimumVersion' -or [version]$config.Headers[$key].MinimumVersion -lt $PSVersionTable.PSVersion) { $data = [Curl2PSParameterDefinition]@{ Type = $config.Headers[$key].Type ParameterName = $config.Headers[$key].ParameterName Value = $data.Value.Values[0] } } } $data if ($argConfig.AdditionalParameters) { foreach ($addParam in $argConfig.AdditionalParameters) { [Curl2PSParameterDefinition]@{ Type = $addParam.Type ParameterName = $addParam.ParameterName Value = Invoke-Command -ScriptBlock $addParam.Value -ArgumentList $paramValue } } } if ($argConfig.Warning.Length -gt 0) { Write-Warning "For param '$($ogParamName)': $($argConfig.Warning)" } } else { Write-Warning "'$paramName' may be a valid cURL parameter, but it has not yet been implemented in Curl2PS. Feel free to open a feature request at https://github.com/theposhwolf/curl2ps/issues" } } Function ConvertTo-HashtableString { param ( [Hashtable]$InputObject, [int]$Depth = 0, [switch]$IsForm ) $strKeys = @() $indent = " " * $Depth # Indentation based on depth foreach ($key in $InputObject.Keys | Sort-Object) { $value = $InputObject[$key] if ($value -is [Hashtable]) { # recursively process nested hashtable $nestedHashtableString = ConvertTo-HashtableString -InputObject $value -Depth ($Depth + 1) $strKeys += "$indent '$key' = $nestedHashtableString" } else { # depth based nesting if ($IsForm.IsPresent -and $value -like 'Get-Item *') { $strKeys += "$indent '$key' = $value" } else { $strKeys += "$indent '$key' = '$value'" } } } $str = "$indent@{`n" + ($strKeys -join "`n") + "`n$indent}" $str } Function Invoke-GetItemInHashtable { param ( [Hashtable]$ht ) # create a new hashtable to store the resolved values $newHt = @{} foreach ($key in $ht.Keys) { $value = $ht[$key] if ($value -is [Hashtable]) { # recursively process nested hashtables and add the result to the new hashtable $newHt[$key] = Invoke-GetItemInHashtable -ht $value } elseif ($value -is [string] -and $value.StartsWith('Get-Item ')) { # curl uses the @ symbol in -F to denote files to send. Invoke-RestMethod # then expects the FileInfo object (Get-Item) so we need to execute it. $scriptBlock = [scriptblock]::Create($value) $newHt[$key] = & $scriptBlock } else { # copy values as-is $newHt[$key] = $value } } return $newHt # Return the new hashtable } Function parse { $args } Function ConvertTo-Curl2PSSplat { [OutputType([hashtable])] param ( [Parameter( Mandatory, ValueFromPipeline )] [Curl2PSParameterDefinition]$Parameter ) Begin { $splat = @{} } Process { if ($Parameter.Type -eq 'Hashtable') { $ht = @{} if ($splat.Keys -contains $Parameter.ParameterName) { foreach ($key in $splat[$Parameter.ParameterName].Keys) { $ht[$key] = $splat[$Parameter.ParameterName][$key] } } foreach ($key in $Parameter.Value.Keys) { $ht[$key] = $Parameter.Value[$key] } try { $convertedHt = Invoke-GetItemInHashtable $ht } catch { $convertedHt = $ht } $splat[$Parameter.ParameterName] = $convertedHt } else { $splat[$Parameter.ParameterName] = $Parameter.Value } } End { $splat } } Function ConvertTo-Curl2PSString { [OutputType([string])] param ( [Parameter( Mandatory, ValueFromPipeline )] [Curl2PSParameterDefinition]$Parameter ) Begin { $baseStr = "Invoke-RestMethod -Uri '{URI}' -Method {METHOD}" $hts = @{} } Process { switch ($Parameter.Type) { 'String' { if ($Parameter.ParameterName -eq 'Uri') { $baseStr = $baseStr -replace '\{URI\}', $Parameter.Value } elseif ($Parameter.ParameterName -eq 'Method') { $baseStr = $baseStr -replace '\{METHOD\}', $Parameter.Value } else { $baseStr += " -$($Parameter.ParameterName) '$($Parameter.Value)'" } } 'Hashtable' { if ($hts.Keys -notcontains $Parameter.ParameterName) { $hts[$Parameter.ParameterName] = @{} } foreach ($key in $Parameter.Value.Keys) { $hts[$Parameter.ParameterName][$key] = $Parameter.Value[$key] } } 'Switch' { $baseStr += " -$($Parameter.ParameterName):`$$($Parameter.Value.ToString().ToLower())" } 'PSCredential' { $cred = $Parameter.Value if ($cred.GetNetworkCredential().Password.Length -gt 0) { Write-Warning 'This output possibly includes a plaintext password, please treat this securely.' $authStr = "`$cred = [PSCredential]::new('$($cred.UserName)', (ConvertTo-SecureString '$($cred.GetNetworkCredential().Password)' -AsPlainText -Force))`n" } else { "`$cred = Get-Credential -UserName '$($cred.UserName)' -Message 'Please input the password for user $($cred.UserName)'`n" } $baseStr = $authStr + $baseStr + " -Credential `$cred" } default { $baseStr += " -$($Parameter.ParameterName) $($Parameter.Value)" } } } End { foreach ($key in $hts.Keys) { $baseStr += "-$($key) $(ConvertTo-HashtableString $hts[$key] -IsForm:($key -eq 'Form'))" } $baseStr } } Function ConvertTo-IRM { [OutputType([hashtable], ParameterSetName = 'splat')] [OutputType([string], ParameterSetName = 'string')] [cmdletbinding( DefaultParameterSetName = 'splat' )] param ( [Parameter( Position = 0 )] [string]$CurlCommand, [Parameter( ParameterSetName = 'asString' )] [switch]$CommandAsString, [switch]$CompressJSON ) if ($CompressJSON.IsPresent) { Write-Warning 'The CompressJSON switch is no longer valid.' } if ($CommandAsString.IsPresent) { Invoke-Curl2PS -CurlString $CurlCommand -AsString } else { Invoke-Curl2PS -CurlString $CurlCommand } } Function Invoke-Curl2PS { [OutputType([hashtable], ParameterSetName = 'splat')] [OutputType([string], ParameterSetName = 'string')] [OutputType([Curl2PSParameterDefinition[]], ParameterSetName = 'raw')] [cmdletbinding( DefaultParameterSetName = 'splat' )] param ( [Parameter( Mandatory, Position = 0 )] [string]$CurlString, [Parameter( ParameterSetName = 'string' )] [switch]$AsString, [Parameter( ParameterSetName = 'raw' )] [switch]$Raw ) if ($CurlString -match "\n") { $arr = $CurlString -split "\n" $CurlString = ($arr | ForEach-Object { $_.TrimEnd('\').Trim() }) -join ' ' } $splitParams = Invoke-Command -ScriptBlock ([scriptblock]::Create("parse $CurlString")) if ($splitParams[0] -notin 'curl', 'curl.exe') { Throw "`$CurlString does not start with 'curl' or 'curl.exe', which is necessary for correct parsing." } [Curl2PSParameterDefinition[]]$parameters = for ($x = 1; $x -lt $splitParams.Count; $x++) { # If this item is a parameter name, use it # The next item must be the parameter value # Unless the current item is a switch param # If not, increment $x so we skip the next one if ($splitParams[$x] -like '-*') { [string[]]$paramNames = $splitParams[$x].TrimStart('-') if ($splitParams[$x] -match '^-[a-zA-Z]+') { # multiple single char flags [string[]]$paramNames = $paramNames[0][0..$paramNames[0].Length] } # grab the value $paramValue = $splitParams[$x + 1] foreach ($paramName in $paramNames) { ConvertTo-Curl2PSParameter -ParamName $paramName -ParamValue $paramValue } } elseif ($splitParams[$x].Trim() -match '^https?\:\/\/') { # the url in curl is the last parameter, so we need to check if it is a valid URL [System.Uri]$uri = $splitParams[$x] if ($uri.UserInfo.Length -gt 0) { ConvertTo-Curl2PSParameter -ParamName 'u' -ParamValue $uri.UserInfo [System.Uri]$uri = $uri.OriginalString -replace "$($uri.UserInfo)@", '' } [Curl2PSParameterDefinition]@{ Type = 'String' ParameterName = 'Uri' Value = $uri.OriginalString } } } # if no explicit method, assume GET if ($parameters.ParameterName -notcontains 'Method') { $parameters += [Curl2PSParameterDefinition]@{ Type = 'String' ParameterName = 'Method' Value = 'Get' } } if ($PSCmdlet.ParameterSetName -eq 'splat') { # generate a splat representation of the parameters $parameters | ConvertTo-Curl2PSSplat } elseif ($PSCmdlet.ParameterSetName -eq 'string') { # generate a string representation of the Invoke-RestMethod command $parameters | ConvertTo-Curl2PSString } elseif ($PSCmdlet.ParameterSetName -eq 'raw') { $parameters } } $script:config = @{ ParameterTransformers = @{ "H" = [Curl2PSParameterTransformer]@{ ParameterName = "Headers" Description = "Headers are passed to curl as name:value and Invoke-RestMethod takes them as a hashtable." Type = "Hashtable" Value = { $split = ($args[0].Split(':') -replace '\\"', '"') @{ ($split[0].Trim()) = (($split[1..$($split.count)] -join ':').Trim()) } } } "header" = "H" "X" = [Curl2PSParameterTransformer]@{ ParameterName = "Method" Description = "Method is simply a string." Type = "String" Value = { $args[0].Trim() } } "request" = "X" "d" = [Curl2PSParameterTransformer]@{ ParameterName = "Body" Description = "Body is a string, some curl json escapes the double quote, so that is removed." Type = "String" Value = { $args[0].Trim() -replace '\\"', '"' } } "data" = "d" "url" = [Curl2PSParameterTransformer]@{ ParameterName = "Uri" Description = "Uri is simply a string." Type = "String" Value = { $args[0].Trim() } } "k" = [Curl2PSParameterTransformer]@{ MinimumVersion = "6.0" ParameterName = "SkipCertificateCheck" Description = "If -k or --insecure is present, -SkipCertificateCheck is always true." Type = "Switch" Value = { $true } } "insecure" = "k" "v" = [Curl2PSParameterTransformer]@{ ParameterName = "Verbose" Description = "If -v or --verbose is present, use -Verbose in Invoke-RestMethod." Type = "Switch" Value = { $true } } "verbose" = "v" "u" = @( [Curl2PSParameterTransformer]@{ ParameterName = "Headers" Description = "Supported in all versions of PowerShell, we can convert basic auth to an Authorization header and pass that." Type = "Hashtable" Value = { $encodedAuth = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($args[0])) @{ Authorization = "Basic $encodedAuth" } } }, [Curl2PSParameterTransformer]@{ MinimumVersion = "7.0" ParameterName = "Credential" Description = "Starting in PowerShell 7.0 (unsure on version), basic auth can be passed using a combination of -Credential and '-Authentication Basic'" Type = "PSCredential" Value = { $user = $args[0] if ($user -like '*:*') { $split = $user.Split(':') if ($split[1].Length -gt 0) { [pscredential]::new($split[0], (ConvertTo-SecureString $split[1] -AsPlainText -Force)) } else { [pscredential]::new($split[0], [securestring]::new()) } } else { Write-Warning "Unable to handle the user authentication value. Unrecognized format." } } AdditionalParameters = @{ ParameterName = "Authentication" Type = "String" Value = { "Basic" } } } ) "user" = "u" "F" = [Curl2PSParameterTransformer]@{ MinimumVersion = "7.0" ParameterName = "Form" Description = "Form is passed as '-F name=value' in curl and needs to be converted to a hashtable for PowerShell." Type = "Hashtable" Value = { $ht = @{} $formData = $args[0].TrimStart('"').TrimEnd('"') $split = $formData.Split('=') $split[1] = $split[1] -replace '@', 'Get-Item ' if ($split[1] -like '{*}') { $ht[$split[0]] = $split[1] | ConvertFrom-Json -AsHashtable } else { $ht[$split[0]] = $split[1] } $ht } Warning = "Form support needs testing! If this works or not, please share your feedback: https://github.com/theposhwolf/curl2ps/issues" } "form" = "F" } Headers = @{ "Content-Type" = @{ MinimumVersion = "7.0" ParameterName = "ContentType" Type = "String" } } } |