lib/TMD.PowerShell.ps1


function Test-TMDIsRunningAction {
    $Global:TmdRunningAction ?? $false
}

Function Import-TMDProvider {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][String]$Provider
    )
    
    ## Get the PS module Root folder
    $ModuleFolder = (Get-Module -ListAvailable TMD.Common).path | Split-Path -Parent
    
    $Provider = $Provider.Replace(' ', '-')
    $extension = '.ps1'
        
    $FilePath = Join-Path $ModuleFolder 'Providers' ($Provider + $extension)
        
    if (Test-Path -Path $FilePath) {
        . $FilePath
    }
}
Function Invoke-WindowsPowerShell {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 0)][PSObject]$Params,
        [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 0)][scriptblock]$ScriptBlock
    )

    $WpsSession = New-PSSession -ComputerName ($env:COMPUTERNAME + '.' + $env:USERDNSDOMAIN) -ConfigurationName  TMD51 -EnableNetworkAccess -Authentication Kerberos
    $StdOut = Invoke-Command -ScriptBlock $ScriptBlock -Session $WpsSession -ArgumentList $Params

    if ($null -ne $StdOut) { 
        $ReturnObjects = $StdOut | Select-Object -Last 1 | ConvertFrom-Json -Depth 100
  
        ## Create each of the items as a Variable object with the name
        foreach ($obj in $ReturnObjects.PSObject.Properties) {
            New-Variable -Name $obj.Name -Value $obj.Value -Force -Scope Global
            # $obj
        }
    }
}

#========================================================================
# Created By: Anders Wahlqvist
# Website: DollarUnderscore (http://dollarunderscore.azurewebsites.net)
#========================================================================

function Connect-PSSessionWhenReady {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ValueFromPipeline)][System.Management.Automation.Runspaces.PSSession]$Session,
        [Parameter(Mandatory = $False, ValueFromPipeline)][Int]$MaxRetries = 51

    )
    
    ## Start a loop
    $ConnectionAttempts = 0
    $SessionStateReady = $false
    While ($SessionStateReady -eq $false) {
    
        ## Set a Max Connection attempt limit
        if ($ConnectionAttempts -eq $MaxRetries) {
            $Session = New-TMDPSSession -Connect
            return
            # throw ('TMD is unable to connect to the TMD PowerShell Session ' + $Session.InstanceId + '. Please restart TMD and try again.')
        }

        ## Open the session or report the status if unable. This is a combination of Session STATE and AVAILABILITY
        switch ($Session.State) {
            "Opened" { 
                if ($Session.Availability -eq 'Available') {
                    $SessionStateReady = $True
                    break
                }
            }
            "Disconnected" { 
                switch ($Session.Availability) {
                    "Available" { 
                        Connect-PSSession $Session -ErrorAction 'SilentlyContinue' | Out-Null
                        if ($Session.Availability -eq 'Available') { 
                            $SessionStateReady = $true
                        }
                        break
                    }
                    "None" { 
                        Connect-PSSession $Session -ErrorAction 'SilentlyContinue' | Out-Null
                        if ($Session.Availability -eq 'Available') { 
                            $SessionStateReady = $true
                        }
                        break
                    }
                    "Busy" { 
                        break
                    }
                    Default {
                        break
                    }
                }
            }
            "Broken" {
                ## The session is broken and is no longer functioning Remove it and create a new one
                Remove-PSSession $Session
                $Session = New-TMDPSSession
                $SessionStateReady = $True
                break
            
            }
            Default {
                Write-Verbose ("TMD-PS-Watcher: Session State is " + $Session.State)
                break
            }
        }
    
        ## If SessionReadyState is not ready
        if (-Not $SessionStateReady) {
        
            ## Start a Sleep Delay
            $SleepTime = (50..150 | Get-Random)
            Start-Sleep -Milliseconds $SleepTime
            $ConnectionAttempts++

            ## Log Debugging Connecting attempts
            if ($ConnectionAttempts -eq 5) { Write-Host "SessionManager unable to connect 5 times" }
            if ($ConnectionAttempts -eq 50) { Write-Host "SessionManager unable to connect 50 times" }
            
            # ## The loop above should have connected to PowerShell, but if it didn't throw an error
            # if ($ConnectionAttempts -eq $MaxRetries) {
                
            # # throw ('Unable to connect to PowerShell Session' + $Session.InstanceId)
            # }
        }
    }
    
    

}
## Provide a simple interface to get/create a TMD_<username> session on the local computer
Function Get-TMDPSSession {
    [CmdletBinding()]
    param (
        [Parameter()][Switch]$Connect,
        [Parameter()][Switch]$Disconnect,
        [Parameter()][Switch]$Enter
    )

    <#
            # Stopping and starting TMD is unpredicitble since the PowerShell session could be affected by MANY circumstances like:
            # Are there still active running jobs
            # Did the computer hybernate
            # Change in network connectivity (VPN changes, blah blah)
            # etc.
            #>

    
    if ($IsWindows) {

        $SessionManagerInstanceId = Get-ItemProperty -Path 'HKCU:\SOFTWARE\TransitionManager' | Select-Object -ExpandProperty 'SessionManagerInstanceId'
                
        ## Get any existing PS Session started earlier
        $TmdUserSession = Get-PSSession -ConfigurationName 'TMD' -ComputerName 'localhost' -InstanceId $SessionManagerInstanceId -ErrorAction 'SilentlyContinue'
                
        ## There may be a broken session. If there is, remove it and create a new one.
        if ($TmdUserSession.State -eq 'Broken') {
                    
            ## The session is broken and is no longer functioning Remove it and create a new one
            Remove-PSSession $TmdUserSessions
            $TmdUserSession = New-TMDPSSession
        } 
                
        ## If there is no TMD session Found, try creating one
        if (-Not $TmdUserSession) {
            $TmdUserSession = New-TMDPSSession
        }    
                
        ## Check again if to see if there was a failure above
        if (-Not $TmdUserSession) {
            Throw 'Unable to create a PowerShell Session.'
        }    
                
        ## Handle Switches to interact with the session, or just return it
        if ($Connect) {
            Write-Host "Connecting to Session" -NoNewline
            Connect-PSSessionWhenReady $TmdUserSession | Out-Null
            Write-Host '...Connected'
        }
        if ($Disconnect) {
            Disconnect-PSSession $TmdUserSession | Out-Null
            Write-Host "Session Disconnected"
        } elseif ($Enter) {
            Write-Host "Entering Session"
            Connect-PSSessionWhenReady $TmdUserSession | Out-Null
            Enter-PSSession $TmdUserSession
        } else {
                    
            ## If the session wasn't Connected, Disconnected or Entered, Return the Session
            return $TmdUserSession
        }
    }  
           
    ## Get a Mac Session
    if ($IsMacOS) {

        ## Get any existing PS Session started earlier
        $KeyFilePath = (Join-Path $userPaths.credentials '.ssh' 'id_rsa')
        $TmdUserSession = New-PSSession -HostName 'localhost' -UserName $ENV:USER -KeyFilePath $KeyFilePath
                
        ## There may be a broken session. If there is, remove it and create a new one.
        if ($TmdUserSession.State -eq 'Broken') {
                    
            ## The session is broken and is no longer functioning Remove it and create a new one
            Remove-PSSession $TmdUserSessions
            $TmdUserSession = New-TMDPSSession
        } 
                
        ## If there is no TMD session Found, try creating one
        if (-Not $TmdUserSession) {
            $TmdUserSession = New-TMDPSSession
        }    
                
        ## Check again if to see if there was a failure above
        if (-Not $TmdUserSession) {
            Throw 'Unable to create a PowerShell Session.'
        }    
                
        ## Handle Switches to interact with the session, or just return it
        if ($Connect) {
            Write-Host "Connecting to Session" -NoNewline
            Connect-PSSessionWhenReady $TmdUserSession | Out-Null
            Write-Host '...Connected'
        }
        if ($Disconnect) {
            Disconnect-PSSession $TmdUserSession | Out-Null
            Write-Host "Session Disconnected"
        } elseif ($Enter) {
            Write-Host "Entering Session"
            Connect-PSSessionWhenReady $TmdUserSession | Out-Null
            Enter-PSSession $TmdUserSession
        } else {
                    
            ## If the session wasn't Connected, Disconnected or Entered, Return the Session
            return $TmdUserSession
        }
    }  
}
    

function New-TMDPSSession {
    [CmdletBinding()]
    param (
        [Parameter()][Switch]$Disconnect,
        [Parameter()][Switch]$Enter
    )

    ## Connect a Windows Session
    if ($IsWindows) {

        ## Create Session Name and create the session with environment defaults
        $TmdSessionConfigurationName = 'TMD'
        $TmdUserSession = New-PSSession -ConfigurationName $TmdSessionConfigurationName -ComputerName localhost -EnableNetworkAccess
    
        # Attempt Kerberos Authentication
        if (-not $TmdUserSession) {
            $TmdUserSession = New-PSSession -ConfigurationName $TmdSessionConfigurationName -ComputerName localhost -EnableNetworkAccess -ErrorAction SilentlyContinue -Authentication Kerberos | Out-Null
        }
        ## Attempt Negotiating Authentication
        if (-not $TmdUserSession) { 
            $TmdUserSession = New-PSSession -ConfigurationName $TmdSessionConfigurationName -ComputerName localhost -EnableNetworkAccess -ErrorAction SilentlyContinue -Authentication Negotiate | Out-Null
        }
        ## Revert to Explicit Default Authentication, in case the user has a custom configuration
        if (-not $TmdUserSession) {
            $TmdUserSession = New-PSSession -ConfigurationName $TmdSessionConfigurationName -ComputerName localhost -EnableNetworkAccess -ErrorAction SilentlyContinue -Authentication 'Default' | Out-Null
        }
 
        ## If a TMD User Session isn't connected yet, throw an error.
        if (-not $TmdUserSession) {
            Write-Host 'PSSessionManager||Status||Session Creation Failed, Restart WinRM'
            Throw "Unable to create a TMD User Session. Please restart TMD."
        }

        ## Record the SessionInstanceID in the Registry for other processes to use
        Set-ItemProperty -Path "HKCU:\Software\TransitionManager" -Name "SessionManagerInstanceId" -Value $TmdUserSession.InstanceId

        ## While Connected, ensure the Session has a SessionCache Variable
        Invoke-Command -Session $TmdUserSession -ScriptBlock {
            
            ## Import TMD
            Import-Module TMD.Common

            ## Import TransitionManager Module
            Import-Module TransitionManager
    
            ## Import PoshRSJobs
            Import-Module PoshRSJob

        }
        
        ## Handle Switches to interact with the session, or just return it
        if ($Disconnect) {
            Disconnect-PSSession $TmdUserSession | Out-Null
            Write-Host "Session Disconnected"
        } elseif ($Enter) {
            Write-Host "Entering Session"
            Enter-PSSession $TmdUserSession
        } else {
                
            ## Return the new session
            return $TmdUserSession
        }
    }

    ## Connect on a Mac
    if ($IsMacOS) {
        
        ## Create an SSH based session to PowerShell on the SSH endpoint
        $KeyFilePath = (Join-Path $env:HOME '.ssh' 'id_rsa')
        $TmdUserSession = New-PSSession -HostName 'localhost' -UserName $ENV:USER -KeyFilePath $KeyFilePath
        if (-not $TmdUserSession) {
            Write-Host 'PSSessionManager||Status||Session Creation Failed, Restart TMD and Try again.'
            Throw "Unable to create a TMD User Session. Restart TMD"
        }

        ## While Connected, ensure the Session has a SessionCache Variable
        Invoke-Command -Session $TmdUserSession -ScriptBlock {
            
            ## Import TMD
            Import-Module TMD.Common

            ## Import TransitionManager Module
            Import-Module TransitionManager
    
            ## Import PoshRSJobs
            Import-Module PoshRSJob

        }
        
        ## Return the new session
        return $TmdUserSession
    }
}