2

With Powershell 7 there have been several changes to Invoke-RestMethod. I've written some modules that are wrappers for several Invoke-RestMethod calls to simplify the interaction with our API for my colleagues. In Powershell 5.1 this code runs fine:

Invoke-RestMethod -Method Get -Uri "http://localhost/MyServer/MyApi" -UseDefaultCredentials

In Powershell 7 this generates an error because I'm using an HTTP resource rather than HTTPS:

Invoke-RestMethod: The cmdlet cannot protect plain text secrets sent over unencrypted connections. To suppress this warning and send plain text secrets over unencrypted networks, reissue the command specifying the AllowUnencryptedAuthentication parameter.

To get around this you just need to add -AllowUnencryptedAuthentication to the call and it works, but that only works for Powershell 7. If I add the flag in a Powershell 5.1 environment it generates an error because the flag doesn't exist:

Invoke-RestMethod : A parameter cannot be found that matches parameter name 'AllowUnencryptedAuthentication'.

There is a workaround by doing the following, but making this change to every single module is going to be a PITA since the Invoke-RestMethod varies from module to module:

if ($PSVersionTable.PSVersion.Major -gt 5) {
    Invoke-RestMethod -Method Get -Uri "http://localhost/MyServer/MyApi" -UseDefaultCredentials -AllowUnencryptedAuthentication
}
else {
    Invoke-RestMethod -Method Get -Uri "http://localhost/MyServer/MyApi" -UseDefaultCredentials
}

I know that you can pass values like -Verbose using something like $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent as described here but I need to completely remove the flag if I'm in a 5.1 or earlier environment and add it for 7 or later. Is there a way to do this in one line or do I need to make the above change to every module?

Tanaka Saito
  • 943
  • 1
  • 17
  • 40
  • 1
    Maybe `if($IsCoreCLR) { $PSDefaultParameterValues['Invoke-RestMethod:AllowUnencryptedAuthentication'] = $true }` then you don't need a condition for Windows PowerShell – Santiago Squarzon Nov 29 '22 at 04:39
  • Holy, this is amazing, I had no idea you could specify default parameters like this! This opens up a lot of other opportunities, thank you so much! If you post this as an answer I'll accept it :) – Tanaka Saito Nov 29 '22 at 05:52
  • 1
    You can also build a hashtable of parameters and "splat" them for a specific call to a cmdlet - see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.3 - similar to @SantiagoSquarzon's comment you just detect the powershell version and add keys to the hashtable as appropriate. – mclayton Nov 29 '22 at 10:25
  • Ah, yeah you mean like if $IsCoreCLR I add it to the hast table, and for the final step in all my modules I just splat them for that call? That might actually be helpful in other ways too for me, thanks! – Tanaka Saito Nov 29 '22 at 12:49
  • @TanakaSaito I added mclayton's approach to my answer for reference tho I believe you already understood how it would go – Santiago Squarzon Nov 29 '22 at 13:52

1 Answers1

2

You could do it using only one condition that checks if the version is PowerShell Core, and if it is, add AllowUnencryptedAuthentication = $true as default value using the preference variable $PSDefaultParameterValues:

if($IsCoreCLR) {
    $PSDefaultParameterValues['Invoke-RestMethod:AllowUnencryptedAuthentication'] = $true
}

A simple example using a temporary module with an advanced function:

$PSDefaultParameterValues = @{
    'Test-PSDefaultParameterValues:param1' = 'hello'
    'Test-PSDefaultParameterValues:param2' = 'world!'
}

New-Module -Name temp -ScriptBlock {
    function Test-PSDefaultParameterValues {
        [CmdletBinding()]
        param($param1, $param2)

        "$param1 $param2"
    }
} -Function Test-PSDefaultParameterValues | Import-Module

Test-PSDefaultParameterValues

You could also use Splatting as mclayton pointed out in a comment. This way would be preferable in my opinion instead of updating the session state of a possible user of your module, however this would also need to be updated in all your function calls:

# this would be the default params for calling the cmdlet
$param = @{
    Method = 'Get'
    Uri    = 'http://localhost/MyServer/MyApi'
    UseDefaultCredentials = $true
}

# then we check if this is Core, and then add the missing
# parameter to the splat hashtable
if($IsCoreCLR) {
    $param['AllowUnencryptedAuthentication'] = $true
}

Invoke-RestMethod @param
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Thanks for adding both answers! I'm having some serious consideration of moving to splatting because that will mean my error handling/printing becomes much easier to deal with (as I can print things like $Body or $URI when I modify them, error messages would be identical in code). Very helpful! – Tanaka Saito Nov 30 '22 at 00:40
  • 1
    @TanakaSaito indeed, you would also avoid updating the session state of a potential user. `$PSDefaultParameterValues` is great, not gonna lie, but should in an ideal world, be used for personal purposes and not be included as part of a module requirement – Santiago Squarzon Nov 30 '22 at 00:42