Powershell leaving RemoteDrive Sessions open - powershell

Minimal Question:
How do I properly dispose of the remote Session Connection left behind after doing:
$session = New-PSSession -ComputerName $VM -Credential $CurrentUser
Invoke-Command -Session $session -ScriptBlock {
$drive = New-PSDrive -Credential $Using:CurrentUser "dummyDriveName" -Root (Split-Path $Using:TargetPath) -PSProvider "FileSystem"
Set-Location $Using:TargetPath
}
Invoke-Command -Session $session -ScriptBlock {
Remove-PSDrive "dummyDriveName"
}
Remove-PSSession -Session $session
I'm running code that looks roughly like this:
$VMs = #(
"vm1.foo.lan",
"vm2.foo.lan",
"vm3.foo.lan"
)
$TargetPath = "\\$env:ComputerName\bar\bin\Debug"
$CurrentUser = (Get-Credential -Credential $env:UserName)
[System.Management.Automation.Runspaces.PSSession[]]$Sessions = #()
foreach ($VM in $VMs) {
$session = New-PSSession -ComputerName $VM -Credential $CurrentUser
$Sessions = $Sessions + $session
Invoke-Command -Session $session -ScriptBlock {
$drive = New-PSDrive -Credential $Using:CurrentUser "dummyDriveName" -Root (Split-Path $Using:TargetPath) -PSProvider "FileSystem"
Set-Location $Using:TargetPath
#Actually do something here, but it's not relevant ... I can reproduce with this line commented out.
}
}
# Wait until Target.exe are known to be complete.
foreach ($session in $Sessions) {
Invoke-Command -Session $session -ScriptBlock {
Remove-PSDrive "dummyDriveName"
}
Remove-PSSession -Session $session
}
My intent is to get a set of remote machines to all invoke an exe sitting on my machine, exposed via a remote share.
Broadly speaking, I:
Connect to the remote machine as myself.
Capture that connection.
Set up a remote drive connecting it to me
(This is REQUIRED to avoid the double hop issue without resorting to CredSsp See: https://learn.microsoft.com/en-us/powershell/scripting/setup/ps-remoting-second-hop)
Do some stuff
Repeat for all machines.
Wait until remote processes are completed.
Reconnect to all the machines to remove the drive and the connection session.
At the end of this, I still have:
These sessions do eventually seem to decay, but not reliably, and it's been able to saturate the max # sessions allowed and thus cause errors saying:
No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept
+ CategoryInfo : InvalidOperation: (dummy:PSDriveInfo) [New-PSDrive], Win32Exception
+ FullyQualifiedErrorId : CouldNotMapNetworkDrive,Microsoft.PowerShell.Commands.NewPSDriveCommand
+ PSComputerName : build7.foo.lan
UPDATE:
Thanks to #jrider who has suggested Get-SmbSession.
Running that after the rest of my script returns:
PS C:\WorkingDirectoty> Get-SmbSession
SessionId ClientComputerName ClientUserName NumOpens
--------- ------------------ -------------- --------
773228331225 10.xxx.yyy.89 FOO\MDM 785
773228331233 10.xxx.yyy.60 FOO\MDM 637
773228331245 10.xxx.yyy.89 FOO\MDM 239
773228331253 10.xxx.yyy.54 FOO\MDM 136
773228331261 10.xxx.yyy.54 FOO\MDM 882
773228331269 10.xxx.yyy.60 FOO\MDM 389
I obviously don't want this script to blindly close EVERY session irrespective of whether it relates to this script, so I guess I want to map my sessions to IP addresses and close anything with that IP address?
Does anyone JustKnow the necessary PowerShell incantation to achieve that?

To Close the SMB sessions by computer name(Updated to include Brondahl's suggestion):
$vmName = $env:ComputerName
$IP = [System.Net.Dns]::GetHostAddresses($vmName).IPAddressToString
Get-SmbSession | Where-Object {$_.ClientComputerName -eq $IP} | Close-SmbSession -Force

For final reference, my complete cleanup code looks like this:
foreach ($session in $Sessions) {
Write-Host ""
$vmName = $session.ComputerName
$vmIPAddress = [System.Net.Dns]::GetHostAddresses($vmName).IPAddressToString
Write-Host "*** Killing any active Target.exe processes on $vmName ***"
Invoke-Command -Session $session -ScriptBlock {
Stop-Process -Name "Target" -Force
}
Write-Host "*** Disconnecting the remote Drive created to give $vmName easy access to $RepositoryShare***"
Invoke-Command -Session $session -ScriptBlock {
Remove-PSDrive "dummyDriveName"
}
Write-Host "*** Closing any open PS connections to $vmName ***"
Remove-PSSession -Session $session
Write-Host "*** Closing the still-open Windows Share connection from $vmName to $env.ComputerName ***"
Get-SmbSession | Where-Object {$_.ClientComputerName -eq $vmIPAddress} | Close-SmbSession -Force
}

Related

Trying to verify that a remote server can access CIF share

I am trying to verify that a remote server has access to a CIF share
$RemoteMoteMachineName = 'ServerName'
$ShareToAccess='\\Share\Level1Folder'
$RemoteSession = New-PSSession -ComputerName $RemoteMachineName
$RemoteTest = Invoke-Command -Session $RemoteSession -ScriptBlock {Test-Path -PATH -ArgumentList $using:ShareToAccess}
If I run the above commands manually with hard coded values, it works.
If I try to use the variables I receive
Test-Path: A positional parameter cannot be found that accepts argument '\Share\Level1\Folder'
Ideas?
Resolved, used instead (create a fake drive and remove if successful:
$RemoteTest = Invoke-Command -Session $RemoteSession -ScriptBlock {New-PSDrive -Name TestName -PSProvider FileSystem -Root $using:ShareToAccess}
If ($RemoteTest){
Write-Host "Remote test pass $RemoteMachine to $ShareToAccess"
Invoke-Command -Session $RemoteSession -ScriptBlock {Remove-PSDrive -Name TestName }
Else {Write-Host "Share access Fail"}

PSWindowsUpdate gets Acces Denied on Remote Machienes while Domain Admin

I want to deploy Updates to Windows Servers in Our Domain.
To achieve this i want to use the Module "PSWindowsUpdate" Here is the Official Release.
I use this Module in combination with PSSessions and import it locally on all Servers outside of the default Module Path.
It should accept the updates and install them without rebooting. This Script is run using an Domain Administrator
After it Accepts the Updates it should start downloading where this happens: The Error of the Job
I started getting this error after the 2018 July Security Patch installed.
As I can't share all of the code because of Company reasons, here is the part that matters:
function invokeUpdate{
param(
$session
)
if($Script:My.Reboot.isChecked){
$job = Invoke-Command -Session $session -ScriptBlock {Import-Module "C:\Scripts\updateModule\$($Using:My.ModuleVersion)\PSWindowsUpdate"; get-windowsupdate -install -AcceptAll} -AsJob
}else {
$job = Invoke-Command -Session $session -ScriptBlock {Import-Module "C:\Scripts\updateModule\$($Using:My.ModuleVersion)\PSWindowsUpdate"; get-windowsupdate -install -ignoreReboot -AcceptAll} -AsJob
}
return $job
}
function initSession{
param(
$serverHostname
)
$ses = New-PSSession -Computername $serverHostname
if(!(Invoke-Command -Session $ses -ScriptBlock {Test-Path "C:\Scripts\updateModule\" })){
Copy-Item "$modpath\$($Script:My.ModuleVersion)" -Destination "C:\Scripts\updateModule\$($Script:My.ModuleVersion)" -ToSession $ses -Recurse
}
Invoke-Command -Session $ses -ScriptBlock {
if((Get-ChildItem -Path "C:\Scripts\updateModule\").count -gt 1){
Get-ChildItem | Where-Object Name -NotLike "$($Using:My.ModuleVersion)" | Remove-Item -Recurse -Force
}
}
return $ses
}
$sessions = [System.Collections.ArrayList]#()
$Script:My.ModuleVersion = "2.1.1.2"
foreach ( $server in $Script:My.ServerActive.Items){
$sessions.Add( (initSession -serverHostname $server) )
}
foreach ($ses in $sessions){
invokeUpdate -session $ses
}
$Script:My.ServerActive.Items :
contains a list of server fqdns
Any Ideas or Solutions would save me,
thanks!
Nik
Edit 1:
Here is the Error Message:
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
+ CategoryInfo : NotSpecified: (:) [Get-WindowsUpdate], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,PSWindowsUpdate.GetWindowsUpdate
+ PSComputerName : fs02.azubi.netz
This will break my Sessions, but the output is $true
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
Invoke-Command : Cannot bind parameter 'Session'. Cannot convert value "True" to type "System.Management.Automation.Runspaces.PSSession". ...
To Fix This Problem I had to change the way of Copying to the other System and the Actual call of get-windowsupdate.
The Mooudle has to be in $env:PSModPath, so to fix it you have to copy into one of those folders.
Copy-Item "$modpath\$($Script:My.ModuleVersion)\" -Destination "$psmod\PSWindowsUpdate\$($Script:My.ModuleVersion)" -ToSession $ses -Recurse -ErrorAction Stop
the Update doesnt need to run Over Invoke Command.
Get-WindowsUpdate -AcceptAll -Install -IgnoreReboot -ComputerName $session.ComputerName
Hope this will Help if you get a similar Problem!

How to get local computer name after New-PSSession -Computername?

I am getting the below error, please advise how to fix this error for null-valued expression
You cannnot call a method on a null-valued expression
+CategoryInfo : InvalidOoperation: (:)[], RuntimeException
+FullyQualifiedErrorId: InvokeMethodonNull
+PSComputerName: DC1
Code below
function myfunction (){
$remoteserver = 'DC1'
$Session = New-PSSession -Computername $remoteserver -Credential $Cred
Import-Module ActiveDirectory
$local= $env:COMPUTERNAME
Invoke-Command -ComputerName $remoteserver -Credential $cred -ScriptBlock
{$using:local
if($local.substring(5,3) -imatch "Sys") {
Get-ADComputer $local | Move-ADObject -Targetpath "ou=PRD,ou=Servers,dc=com,dc=Companycorp,dc=net"}
}
} #end function
Invoke-Command -ComputerName $remoteserver -ScriptBlock ${Function:myFunction}
What you're looking for is the $using: scope. If you define variables that you want to use in your remote execution, you need to access them like:
$PC = $env:ComputerName
Invoke-Command -Computer DC01 -ScriptBlock { $using:PC <# logic #> }
If you mean you want to remote into DC01 to run commands against localhost, you're going to run into the second-hop problem due to Kerberos.
Update: Your new example looks pretty convoluted. Here's an example that should work:
$MyPC = $env:ComputerName
$Session = New-PSSession -Credential (Get-Credential) -ComputerName 'DC1'
Invoke-Command -Session $Session -ScriptBlock {
Import-Module -Name 'ActiveDirectory'
$PC = $using:MyPC
If ($PC.Substring(5,3) -eq 'sys')
{
Get-ADComputer -Identity $PC |
Move-ADObject -TargetPath 'ou=PRD,ou=Servers,dc=com,dc=Companycorp,dc=net'
}
}
What I think you're asking is 'how do I open a session on a remote pc, but then still run commands on my local PC'. If that's so, then let's walk through it.
First, we can open a remote connection to another computer in PowerShell by creating a new PSSession, as you're doing here:
$session = New-PSSession -Computername DC01 -Credential $cred
You can then either step into the remote computer wholly using Enter-PSSession, or just send individual commands to the remote computer using:
Invoke-Command -ScriptBlock {#Commands to run on the remote PC}`
-Session $session
Once you enter the remote PC, you can return to your own PC using the Exit-PSSession command.
#Enter Remote PC
Enter-PSSession $session
DC01> hostname
*DC01*
#Step out of Remote PC
Exit-PSSession
PS> hostname
*YOURPCNAME*
If this isn't what you want to do, let me know and we'll get you sorted.
You have to use Invoke-Command :
$session = New-PSSession -Computername DC01 -Credential $cred
Invoke-Command -Session $session -ScriptBlock {
$remoteComputerName = $env:computername
}

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

Invoking remote powershell command not returning all results

I'm pretty new to powershell and I'm trying to invoke a remote powershell command to return the size of a drive but am having trouble.
If I run the below command directly on the server, then I get a result of 4.
import-module virtualmachinemanager
$checks = get-vm -Name DC1 | get-scvmcheckpoint
foreach ($disk in $checks){
$disk.virtualdiskdrives.virtualharddisks.size
}
I then try to run the command remotely using the below, but I don't get a result. The $checks variable contains a number of other values though.
$session = New-PSSession -ComputerName VM01 -ConfigurationName Microsoft.Powershell32 -Credential administrator
$checks = Invoke-Command -Session $session -ArgumentList VM01 -ScriptBlock {
import-module virtualmachinemanager
get-vm -Name DC1 | get-scvmcheckpoint
}
foreach ($disk in $checks){
$disk.virtualdiskdrives.virtualharddisks.size
}
The most likely issue here, (haven't tested the code on actual VMM server) is that objects returned from remote session are Serialized and De-serialized. Here is a reference How objects are sent to and from remote sessions.
To get around this, I would suggest doing all the processing on a remote machine and only return simple PS objects.
$session = New-PSSession -ComputerName VM01 -ConfigurationName Microsoft.Powershell32 -Credential administrator
$checks = Invoke-Command -Session $session -ArgumentList VM01 -ScriptBlock {
import-module virtualmachinemanager
$disks = get-vm -Name DC1 | get-scvmcheckpoint
foreach ($disk in $disks){
$disk.virtualdiskdrives.virtualharddisks.size
}
}
foreach ($disk in $checks){
write-host "Disk size = $disk"
}