Khaelas
Khaelas

Reputation: 37

Logon session issue with Remote PowerShell (Invoke-Command)

I'm trying to configure SAML/ADFSRelyingPartyTrust remotely. I know the script itself works, because I can call the ScriptBlock part myself while logged onto the server, feed it the parameter, and it works.

I want to be able to do this remotely though, and I get the error

[blah20.blah.blah.com] Connecting to remote server blah20.blah.blah.com failed
with the following error message : A specified logon session does not exist. It
may already have been terminated. For more information, see the
about_Remote_Troubleshooting Help topic.
    + CategoryInfo          : OpenError: (blah20.blah.blah.com:String) [], PSRemotingTransportException
    + FullyQualifiedErrorId : 1312,PSSessionStateBroken

I haven't actually tried anything else on account of not really being sure where to tweak. It's my first time trying to do remote commands, but I think this is the right way to do so.

I've seen some mention of needing credentials, but ideally I just wanted it to run as the user that's logged in on their computer.

CLS
$Title = "SAML Server Choice"
$Info = "Please pick the server you wish to configure SAML on"

$options = [System.Management.Automation.Host.ChoiceDescription[]] @("&LIVE", "&TEST")
[int]$defaultchoice = 1
$opt = $host.UI.PromptForChoice($Title , $Info , $Options,$defaultchoice)
switch ($opt) {
    0 { Write-Host "LIVE" -ForegroundColor Green}
    1 { Write-Host "TEST" -ForegroundColor Green}
}

switch ($opt) {
    0 {"SAML Server is blah20.blah.com"; $SAMLServer = "blah20.blah.blah.com"}
    1 {"SAML Server is blah50.blah.com"; $SAMLServer = "blah50.blah.blah.com"}
}

$EnvironmentName = Read-Host "Please enter the customer name: "

$ScriptBlockContent = {
    $EnvironmentURL = 'https://'+$EnvironmentName+'.blah.com';
    $EndPoint = 'https://'+$EnvironmentName+'.blah.com/app_pages/admin/saml.aspx';
    $folderPath = "C:\SAMLAutoSetup\";
    $claimsFilePath = $folderPath + "claims.txt";
    $rulesFilePath = $folderPath + "rules.txt";
    $claims = '@RuleTemplate = "LdapClaims"
               @RuleName = "LDAP"
               c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
                => issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
               "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "http://schemas.xmlsoap.org/claims/Group",
               "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), query =
               ";userPrincipalName,displayName,tokenGroups,mail;{0}", param = c.Value); ';

    $claims | Out-File $claimsFilePath -Force;

    $rules = '@RuleTemplate = "AllowAllAuthzRule"
                                         => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");';

    $rules | Out-File $rulesFilePath -Force; 

    Add-PSSnapin Microsoft.Adfs.PowerShell; 

    if (Get-ADFSRelyingPartyTrust -Name $EnvironmentName) {
        Write-Host `n"$EnvironmentName Relying Party Trust already exists";
    } else {
        Write-Host `n"$EnvironmentName Relying Party Trust doesn't exist! Need to Create";
        $samlEndPoint = New-ADFSSamlEndpoint -Protocol SAMLAssertionConsumer -Uri $EndPoint -Binding POST -IsDefault $false -Index 0;
        Add-ADFSRelyingPartyTrust -Name $EnvironmentName -Identifier $EnvironmentURL -SamlEndpoint $samlEndPoint -IssuanceTransformRulesFile $claimsFilePath -IssuanceAuthorizationRulesFile $rulesFilePath;
        Write-Host `n"$EnvironmentName Created successfully!";
    }
}

Invoke-Command -ComputerName $SAMLServer -ScriptBlock $ScriptBlockContent -ArgumentList $EnvironmentName

I may have structured the script wholly wrong, pretty new to the whole thing, so open to suggestions on restructuring to make it work!

Upvotes: 1

Views: 2488

Answers (1)

postanote
postanote

Reputation: 16116

This is not a PowerShell code issue. It's an environmental / Auth / permissions one on Windows proper.

PSRemoting must be properly setup on the remote host and you must be a local admin on the remote host to fully leverage PowerShell remoting.

You must properly use your code in an explicit or implicit PSRemoting session. That error message is very specific, please read up on PSRemoting as the message is stating.

As for this …

A specified logon session does not exist.

... it is not unique to PowerShell, this same error can happen for other use cases on Windows.

As for this...

ideally I just wanted it to run as the user that's logged in on their computer.

... you cannot run remote commands in the context of the current logged on user, using native PowerShell. PowerShell will always use the user context that ran the script. That is a Windows proper security boundary. So, if you start this script, it will always be in your user context.

Either the logged on user must run this script directly and have the proper credentials to do so, or you set a scheduled task to do this, again, using the user creds (which you won't know - so, they'd have to set this up).

If you are trying to run code in the context of another user. You need to use something like MS SysInternals PSExec in your script.

For what you are doing the looged on user would still need the ADDS cmdlets and ADFS cmdlets installed / proxied on their host to run this, or they'd need to be on the DC/ADFS server (physically or remotely (RDP/PSRemoting implicit / explicit)) to do so.

Lastly, variable use must be in scope, and you cannot use local variables in a remote session without them being visible in the scope of the calling code. See the help files on the topic.

You also, have a few things that are syntactically (how you are using the line break character is one of them) wrong and all the extra semi-colons are not needed for PowerShell in general. Their are a few cases where they are, but what you are doing is not really one of them.

So, ignoring the ...

A specified logon session does not exist.

... error for a moment. Tweaking your posted code, I'd suggest this. Others of course will have their take on the topic. BTW... untested and I an not in a environment that I can test at this time.

### configure SAML/ADFSRelyingPartyTrust

Clear-Host

<#
Import needed PowerShell modules and must be on the ADFS server and have the 
ADDS cmdlets via the RSAT Tools installed and enabled locally or proxied via 
PowerSehll Remoting
#>

Import-Module -Name ServerManager, ActiveDirectory, ADFS -Force
Add-PSSnapin Microsoft.Adfs.PowerShell

<#
Force environment specifications

about_Requires | Microsoft Docs
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_requires
#>

#Requires -Version 4
#Requires -PSSnapin Microsoft.Adfs.PowerShell
#Requires -Modules ServerManager, ActiveDirectory, ADFS 
#Requires -RunAsAdministrator


<#
Ensure that any local variables are properly scoped for PSRemote sessions

about_scopes | Microsoft Docs
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes

The Using scope modifier

Using is a special scope modifier that identifies a local variable in a remote 
command. Without a modifier, PowerShell expects variables in remote commands to 
be defined in the remote 
session.

The Using scope modifier is introduced in PowerShell 3.0.

For more information, see about_Remote_Variables.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_variables?view=powershell-6

Using local variables

You can also use local variables in remote commands, but you must indicate that 
the variable is defined in the local session.

Beginning in Windows PowerShell 3.0, you can use the Using scope modifier to 
identify a local variable in a remote command.

The syntax of Using is as follows:

$Using:<VariableName>


# Simple strings only need single quotes, variable / string expansion requires double quotes

about_Quoting_Rules | Microsoft Docs
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules

The Difference Between Single and Double Quotes in PowerShell
https://blog.techsnips.io/the-difference-between-single-and-double-quotes-in-powershell
https://www.sconstantinou.com/powershell-quotes/

#>

$Title = 'SAML Server Choice'
$Info = 'Please pick the server you wish to configure SAML on'

$options = [System.Management.Automation.Host.ChoiceDescription[]] @('&LIVE', '&TEST')

[int]$defaultchoice = 1

$opt = $host.UI.PromptForChoice($Title , $Info , $Options,$defaultchoice)

<# 
Using write-Host for colorizing screen text - otherwise Write-Host is not needed.

Output to the screen is the PowerShell default, unless you:
- assign to a variable (and not using varialbe squeezing)
- not use Out-Host
- or use Out-NUll
#>

switch ($opt) {
    0 { Write-Host 'LIVE' -ForegroundColor Green}
    1 { Write-Host 'TEST' -ForegroundColor Green}
}

<#
Using variable squeezing to assign value to the variable and output to screen

Server names are hardcoded here, but the user domain is dynamically discovered
via PowerShell $ENv variable.

One could just as easily discover the DC/ADFS/SAML server FQDN from ADDS to
avoid this hard coding effort.
#>

switch ($opt) {
    0 {"SAML Server is $(($SAMLServer = "blah20.$env:USERDNSDOMAIN"))"}
    1 {"SAML Server is $(($SAMLServer = "blah50.$env:USERDNSDOMAIN"))"}
}

$EnvironmentName = Read-Host 'Please enter the customer name: '

$ScriptBlockContent = {
    $EnvironmentURL = "https://$EnvironmentName.$env:USERDNSDOMAIN"
    $EndPoint = "$EnvironmentURL/app_pages/admin/saml.aspx"

    $folderPath = 'C:\SAMLAutoSetup\'

    $claimsFilePath = "$folderPath claims.txt"
    $claims = '@RuleTemplate = "LdapClaims"
               @RuleName = "LDAP"
               c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
                => issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn",
               "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "http://schemas.xmlsoap.org/claims/Group",
               "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), query =
               ";userPrincipalName,displayName,tokenGroups,mail;{0}", param = c.Value); ';

    $claims | 
    Out-File $claimsFilePath -Force

    $rules = '@RuleTemplate = "AllowAllAuthzRule"
                                         => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");';

    $rulesFilePath = "$folderPath rules.txt"
    $rules | 
    Out-File $rulesFilePath -Force

    <# 
    Special formatting characters, like the new line `n, needs to be properly
    quoted to be used. Avoid unnecessary string concatenation where possible.

    https://leanpub.com/thebigbookofpowershellgotchas/read
    https://leanpub.com/thebigbookofpowershellgotchas/read#leanpub-auto-dontconcatenatestrings
    https://devops-collective-inc.gitbook.io/the-big-book-of-powershell-gotchas/dont-concatenate-strings
    https://github.com/devops-collective-inc/big-book-of-powershell-gotchas

    See also
    PowerShell: Using the -F format Operator
    https://social.technet.microsoft.com/wiki/contents/articles/7855.powershell-using-the-f-format-operator.aspx

    The Unofficial PowerShell Best Practices and Style Guide
    https://github.com/PoshCode/PowerShellPracticeAndStyle
    #>

    if (Get-ADFSRelyingPartyTrust -Name $EnvironmentName) 
    { "`n$EnvironmentName Relying Party Trust already exists"    } 
    else 
    {
        "`n$EnvironmentName Relying Party Trust doesn't exist! Need to Create"

        <#
        Leveraging PowerShell Splatting for readability

        about_Splatting | Microsoft Docs
        https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting
        #>

        $newADFSSamlEndpointSplat = @{
            Protocol = 'SAMLAssertionConsumer'
            IsDefault = $false
            Index = 0
            Uri = $EndPoint
            Binding = 'POST'
        }
        $samlEndPoint = New-ADFSSamlEndpoint @newADFSSamlEndpointSplat

        $addADFSRelyingPartyTrustSplat = @{
            IssuanceAuthorizationRulesFile = $rulesFilePath
            SamlEndpoint = $samlEndPoint
            Name = $EnvironmentName
            Identifier = $EnvironmentURL
            IssuanceTransformRulesFile = $claimsFilePath
        }
        Add-ADFSRelyingPartyTrust @addADFSRelyingPartyTrustSplat


        "`n$EnvironmentName Created successfully!"
    }
}

$invokeCommandSplat = @{
    Credential = (Get-Credential -Credential "$env:USERDOMAIN\$env:USERNAME")
    ComputerName = $SAMLServer
    ArgumentList = $EnvironmentName
    ScriptBlock = $ScriptBlockContent
}
Invoke-Command @invokeCommandSplat

<#
If using Implicit PSRemoting to the DC or a server running the RSAT and ADFS tools, 
from a remote workstation, then you'd not need to use Invoke-Command at all. 
You'd just run the script as if you were on the DC/ADFS server directly.


An Introduction to PowerShell Remoting Part Four: Sessions and Implicit Remoting
https://devblogs.microsoft.com/scripting/an-introduction-to-powershell-remoting-part-four-sessions-and-implicit-remoting
https://devblogs.microsoft.com/scripting/remoting-the-implicit-way

PowerShell Implicit Remoting: Never Install a Module Again
https://www.itprotoday.com/powershell/powershell-implicit-remoting-never-install-module-again
#>

If you are not in an established Implicit / explicit PSRemote session, all that action item code must be in the scriptblock that needs to run on the remote host..

Upvotes: 1

Related Questions