Powershell Remoting: using imported module cmdlets in a remote pssession - powershell

Is there a way to use modules that were imported in a local session in a remote session? I looked at import-pssession, but I don't know how to get the local session. Here's a sample of what I want to do.
import-module .\MyModule\MyModule.ps1
$session = new-pssession -computerName RemoteComputer
invoke-command -session $session -scriptblock { Use-CmdletFromMyModule }
Also, I do not want to import-module in the remote session, as the ps1 files are not on that server.

I ended up hacking this to work. What I did was create a local session, import modules into that session and used import-pssession to import modules from the created local session into the remote session. This is slow. If anyone has a better way of doing this, or if someone knows how to get an instance of the base session I'd love to hear from you!
Remoting.psm1
function Export-ModuleToSession {
Param(
[ValidateNotNull()]
$session,
[ValidateNotNull()]
$modules
)
$computername = $env:computername
$modulesToImport = get-module -name $modules
invoke-command -session $session -argumentlist #($computername, $modulesToImport) -scriptblock {
Param(
$computername,
$modules
)
write-host ("Creating Temp Session On: " + $computername)
$localSession = New-psSession -computername $computername
$modules | foreach-object {
if($_.ModuleType -ne "Binary") {
$path = $_.path
}
else {
$path = join-path (split-path $_.Path) ("{0}.psd1" -f $_.name)
}
invoke-command -session $localSession -argumentList $path -scriptblock {
Param(
$path
)
$initializeDefaultBTSDrive = $false
set-executionpolicy unrestricted
write-host ("Importing Module To Temp Session: " + $path)
import-module $path
}
}
$initializeDefaultBTSDrive = $false
$modules | foreach-object {
write-host ("Exporting Module: " + $_.name)
import-psSession -session $localSession -Module $_.name | out-null
}
}
}
MyModule.psm1
function MyCmdlet {}
RemotingTest.ps1
import-module .\remoting.psm1
import-module .\MyModule.psm1
try
{
$remoteSession = New-PsSession -computerName "RemoteComputer"
Export-ModuleToSession -session $remoteSession -modules "MyModule"
Invoke-Command -session $remoteSession -scriptblock { MyCmdlet } -verbose -ea Stop
}
finally
{
Remove-PsSession $remoteSession -ea Continue
Remove-Module "Remoting" -ea Continue
Remove-Module "MyModule" -ea Continue
}

As an alternate to what Jonathan mentions, if you have source modules you want to push over the wire, then you can do that without too much trouble. If you have binaries, you might be able to do something similar.but I'd say all bets are off there. Essentially you push the files over as params in a hash, write to temp, then import.
function Export-SourceModulesToSession
{
Param(
[Management.Automation.Runspaces.PSSession]
[ValidateNotNull()]
$Session,
[IO.FileInfo[]]
[ValidateNotNull()]
[ValidateScript(
{
(Test-Path $_) -and (!$_.PSIsContainer) -and ($_.Extension -eq '.psm1')
})]
$ModulePaths
)
$remoteModuleImportScript = {
Param($Modules)
Write-Host "Writing $($Modules.Count) modules to temporary disk location"
$Modules |
% {
$path = ([IO.Path]::GetTempFileName() + '.psm1')
$_.Contents | Out-File -FilePath $path -Force
"Importing module [$($_.Name)] from [$path]"
Import-Module $path
}
}
$modules = $ModulePaths | % { #{Name = $_.Name; Contents = Get-Content $_ } }
$params = #{
Session = $Session;
ScriptBlock = $remoteModuleImportScript;
Argumentlist = #(,$modules);
}
Invoke-Command #params
}
Call like
$session = New-PSSession -ComputerName Foo
Export-SourceModulesToSession $session -ModulePaths '.\module.psm1','.\module2.psm1'
Also theoretically possible, exporting a current localhost session to module and pushing that over the wire -- untested pseudo-code. This might not work...
$localSession = New-PSSession #defaults to localhost
# if you don't have modules automatically loading in the profile, etc, then manually load them
Invoke-Command -Computer $localSession -ScriptBlock { Import-Module 'foo'; Import-Module '.\module.ps1' }
Export-PSSession $localSession -OutputModule TempLocalModule
#now that you have TempLocalModule written out, it's possible you can send that thing across the wire in the same way

in case this helps:
if you can switch to PowerShell 3.0, then
the Get-Module and Import-Module cmdlets support modules on remote computers:
http://technet.microsoft.com/en-us/library/hh857339.aspx#BKMK_REM
It should be possible to run PowerShell on remote computer, that performs Import-Module,
without having any ps1 scripts on the remote computer.

So I was looking for something similar... In my case I just needed to export a single function to a remote session... this is what I came up with. Maybe you could loop over it to try it. It doesn't work with internal commands, but it does work on functions in custom modules (in the testing I've done).
function Export-FunctionToSession
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$Session,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$FunctionName
)
$script = "Function $functionName(){" + (Get-Command $functionName).definition + '}'
$scriptBlock = {Invoke-Expression $using:script}
Invoke-Command -Session $session -ScriptBlock $scriptBlock
}

I don't believe you can. You can go the other way - import commands loaded on the remote machine into your remote session running on the local machine. You can specify a script to invoke-command and it will copy that script over to the remote machine and run it. But if you require snapins or additional modules, you will need to make sure those are installed on each remote machine and then load them into the remote session via commands or your script.

I would suggest something like:
$rs = New-PSSession -ComputerName "RemoteComputer"
Invoke-Command -Session $rs -scriptblock {import-module ActiveDirectory}
Import-PSSession -Session $rs -Module ActiveDirectory
Since then, you can use ActiveDirectory cmdlets in your session.
Hope this helps.

use credssp authentication
invoke-command -computername $localSession -Credential $Credential -Authentication Credssp

Related

Problem with test-path on computer from another domain

Im trying to test a remote folder in a computer from another domain with -credential
This command works fine:
Invoke-Command -ComputerName "server" -credential domain\user -ScriptBlock {Test-Path -Path "\\server\s$\temp"}
But if i use it in a script fails:
$servers = Get-Content "servers.txt"
$Path = "\\D$\Temp"
$cred = "domain\user"
ForEach ($server in $servers) {
if (invoke-command -computername $server -credential $cred -ScriptBlock {Test-Path -Path "\\$server\$Path"})
}
PD: All this option works in a server of my domain without specify another credentials.
Besides the syntax issue with missing the script block after your if statement, this should work as long as you specify the variables as remote ones. Use $using or pass it as an argument with -ArgumentList.
$servers = Get-Content "servers.txt"
$Path = "\\c$\Temp"
$cred = "domain\user"
ForEach ($server in $servers) {
if (
invoke-command -computername $server -ScriptBlock {
Test-Path -Path "\\$using:server\$using:Path"
}
) { <#do code here#> }
}
If you run the shell with the proper credentials to begin with, all youd have to do is use Test-Path directly but I understand that you'd like to try using the -Credential parameter.

PowerShell cannot use New-PSSessions right after Invoke-CimMethod (The runspace state is not valid for this operation)

The two code below works independently, however, they cannot work in the same script. I really need help, there's got to be something incompatible.
The first part of my script uses Invoke-CimMethod to Enable-PSRemoting, and it works.
Variables
$hostname = 'PC1'
$Session = New-PSSession $hostname
$DestinationPath = "C:\windows\temp"
Part 1
$SessionArgs = #{
ComputerName = $hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
The second part of my code works if the first part above is not present. It is to create a TEMP folder, and then copy an entire folder into TEMP.
Part 2
Invoke-Command -Session $Session -ScriptBlock { Param($Destination) New-Item -Path $Destination -ItemType Directory -ErrorAction SilentlyContinue} -ArgumentList $DestinationPath
Copy-Item -Path "\\shared\folder\foo\bar" -ToSession $Session -Destination "C:\windows\temp\" -recurse -force
Error
Copy-Item : The runspace state is not valid for this operation.
What's weird is I've inserted the Invoke-CimMethod to many other scripts that does similar things and it works fine, like for example
Example of it working
$env:hostname
$env:process
$SessionArgs = #{
ComputerName = $env:hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
$session = New-PSSession $env:hostname
ipconfig
Invoke-Command -Session $session -ScriptBlock {param($process) Stop-Process -ProcessName $process -Force} -ArgumentList $env:process
$Session | Remove-PSSession
Please help! I've tried everything, I even tried Get-CimSession | Remove-CimSession but that didn't work. Why is it incompatible?
I was able to fix this issue by putting the variable
$Session = New-PSSession $hostname
Right before Invoke-Command because I think when I sent enable-pssession it resets the connection.

Sending out lockdown or general message to all active users in domain, cross-architecture

I'm trying to setup a script which will message all users in domain - Mixture, some are on Windows 10 Surface Pro's, some Citrix VDI.
The Citrix Part at the bottom isnt quite right, when starting a session, i seem to need to import the Citrix modules to the DDC, even though i'm connect via a PS Session (new-pssession) to the controller. The message on the VDI users desktop isn't appearing and, not sure why...
$Cred = Read-Host "enter-username e.g. domain\user.name"
$computers = Get-Content C:\Scripts\allcomputers.txt #| Where-Object { $_ }
#foreach ($computer in $computers) {
# Invoke-Command -computername $computer -scriptblock {msg * "INSERT MESSAGE TO STAFF HERE"} -Credential $cred
}
# Import-Module Citrix.XenDesktop.Admin?
# Add-PSSnapin Citrix?
$s = New-PSSession -cn DDC -Credential DOMAIN\Cred
Invoke-Command -Session $s -ScriptBlock { $sessions = Get-BrokerSession -UserName DOMAIN\User ;
Send-BrokerSessionMessage $sessions -MessageStyle Information -Title TestTitle -Text TestMessage
}
Get-PSSession | Remove-PSSession
If i use the command in the script block in an interactive session, the modules don't appear to be installed, hence the commented out 2 lines... :S
Thanks for the help - Working Code that will send out a message to all Citrix XDT sessions via PowerShell:
$s = New-PSSession -cn CitrixDDC -Credential Domain\Administrator
Invoke-Command -Session $s -ScriptBlock { import-module
Citrix.XenDesktop.Admin; Add-PSSnapin Citrix.*;
$sessions = Get-BrokerSession -UserName Domain\* `
Send-BrokerSessionMessage $sessions -MessageStyle Information -Title
TestTitle -Text TestMessage
}
Get-PSSession | Remove-PSSession

Powershell Passing Function to remote command

I am trying to pass local function to remote code but not getting any success. Below is my code.
#variables defined over here
...
function comparehash ($source, $destination)
{
$sourcefiles = #{}
...
}
$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
$session = New-PSSession -ComputerName $srv -port 22 -Credential $cred –Authentication CredSSP
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application)
#Write-Host "This is" $source
# Take backup of the site first
Copy-Item $source\$application $destination -Recurse -force
${function:comparehash}
} -ArgumentList $site_path_local, $backup_path, $app
Remove-PSSession -Session $session
This code is copying source files to destination. Function has been created to validate md5 sum of the copied files. When I run the script script runs fine but it doesn't call the function code. Is there anything additional need to be done to call function?
Update
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application, $fundef, $comparefunc )
Write-Host "This is" $source, $destination
# Take backup of the site first
#Copy-Item $source\$application $destination -Recurse -force
[ScriptBlock]::($comparefunc).Invoke($source,$destination)
#comparehash $site_path_local $backup_path
} -ArgumentList $site_path_local, $backup_path, $app, ${function:comparehash}
Remove-PSSession -Session $session
Above code is throwing following error:
PS C:\Windows\system32> C:\Users\vijay.patel\Documents\myscripts\Env_Refresh.ps1
This is F:\inetpub F:\Env_Backup\Refresh_Backup\
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (Invoke:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : 172.16.82.124
Since the function is defined in your own local scope, you'll have to pass it along to the remote session as well. Through testing, it seems that it gets passed as a string, but you can use [scriptblock]::Create() to "recreate" it in the remote session:
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application, $comparefunc)
# do stuff
# Now invoke the function that was provided
[ScriptBlock]::Create($comparefunc).Invoke($source,$destination)
} -ArgumentList $site_path_local, $backup_path, $app, ${function:comparehash}
I found the $Using command can be your friend here. If you keep the remote PSSession open, you can transfer all the functions you will need one at a time, then invoke the final scriptblock that calls/uses those functions.
function comparehash($source, $destination)
{
Write-Output "I like hash";
if( $source -eq $destination) { PackThePipe $source; }
}
function PackThePipe($hash)
{
Write-Output "Pushing $hash through the pipe";
}
$session = New-PSSession -ComputerName $srv -port 22 -Credential $cred –Authentication CredSSP
#create the functions we need remotely
Invoke-Command $session { Invoke-Expression $Using:function:comparehash.Ast.Extent.Text; }
Invoke-Command $session { Invoke-Expression $Using:function:PackThePipe.Ast.Extent.Text; }
#call the scriptblock that utilizes those functions
Invoke-Command $session { $s = "12345"; $d="12345"; comparehash $s $d; }
Remove-PSSession $session;

Remote Registry using Enter-PSSession

I am trying to read strings in a remote registry. When I run the script I am working on, it connects to the workstation in the list, but it only reads the local computer when running, not the remote. any Ideas?
#create open dialog box
Function Get-FileName($initialDirectory)
{
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' );
$d = New-Object Windows.Forms.OpenFileDialog;
$d.ShowHelp = $True;
$d.filter = "Comma Separated Value (*.csv)| *.csv";
$d.ShowDialog( ) | Out-Null;
$d.filename;
}
# Set Variables with arguments
$strFile = Get-FileName;
$strComputer = Get-Content $strFile;
$date = Get-Date -Format "MM-dd-yyyy";
$outputFile = "C:\PowerShell\Reports";
$cred = Get-Credential
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
..
..
}
The above code won't work. Enter-PSSession is not for using in a script. Anything written after that in a script won't run.
Instead, use Invoke-Command and pass rest of the script block as a parameter value. For example,
foreach ($computer in $strComputer) {
Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
}
As the comments already explained, Enter-PSSession is for interactive use. To read remote registry entries, there are several ways.
Use plain reg.exe, it works well enough. Like so,
foreach($computer in $strComputers) {
reg query \\$computer\hklm\software\Microsoft\Windows\CurrentVersion\Reliability /v LastComputerName
}
Use PSSessions. Create a session and Invoke-Command to read registry. Like so,
function GetRegistryValues {
param($rpath, $ivalue)
Set-Location $rpath
$systemInfo = (Get-ItemProperty .).$ivalue
Write-Host $systemInfo
}
$session = New-PSSession -ComputerName $computer
Invoke-Command -Session $session -Scriptblock ${function:GetRegistryValues} `
-argumentlist "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability",`
"LastComputerName"
Remove-PSSession $session
Use .Net classes, Microsoft.Win32.RegistryKey. Like so,
$sk = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $server)
$k = $sk.opensubkey("SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability", $false)
write-host $k.getvalue("LastComputerName")