I'm a newbie new employee at an IT firm as a system information administrator and my boss gave me the project of: taking a screenshot of a distant machine in the network without notifying them.
They should not get disconnected from their session as well.
I'm looking for any 3d party software (free or otherwise) or any powershell script to help me through this.
My progress:
PowerShell Script for saving a screenshot locally:
$Path = "C:\Users\Naskez\Desktop\Screenshots"
If (!(test-path $path)) {
New-Item -ItemType Directory -Force -Path $path
}
Add-Type -AssemblyName System.Windows.Forms
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
$image = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
$graphic = [System.Drawing.Graphics]::FromImage($image)
$point = New-Object System.Drawing.Point(0, 0)
$graphic.CopyFromScreen($point, $point, $image.Size);
$cursorBounds = New-Object System.Drawing.Rectangle([System.Windows.Forms.Cursor]::Position, [System.Windows.Forms.Cursor]::Current.Size)
[System.Windows.Forms.Cursors]::Default.Draw($graphic, $cursorBounds)
$screen_file = "$Path\" + $env:computername + "_" + $env:username + "_" + "$((get-date).tostring('yyyy.MM.dd-HH.mm.ss')).png"
$image.Save($screen_file, [System.Drawing.Imaging.ImageFormat]::Png)
Afterwards, If you want to get a desktop screenshot from an RDS server (or a desktop Windows, in which multiple concurrent RDP connections are allowed), you must first get a user session ID on the remote computer. Specify the name of a remote computer/server and a user account in the following PowerShell script:
$ComputerName = "nld-rds1"
$RDUserName = "h.jansen"
$quser = (((query user /server:$ComputerName) -replace '^>', '') -replace '\s{2,}', ',' | ConvertFrom-Csv)
$usersess=$quser | where {$_.USERNAME -like $RDUserName -and $_.STATE -eq "Active"}
$usersessID=$usersess.ID
Then finally excecuting a Psexec with the right sessionID :
.\PsExec.exe -s -i $usersessID \\$ComputerName powershell.exe -executionpolicy bypass -WindowStyle Hidden -file "\\nld-fs01\Screen\CaptureLocalScreen.ps1"
Problem is: my company does not allow multiple RDP connections. So if i try to do this i have to disconnect them.
Any solutions or tip will be much appreciated.
NB:The PowerShell solution listed above is not mine. Original link below:
http://woshub.com/take-user-desktop-screenshot-with-powershell/
Related
Thought I would share this quick function I made for myself, feel free to adapt it and improve it according to your needs.
Sometimes you want to run commands as the logged on user of a remote computer.
As you know, some commands show output for the user who runs it and if you run the same command with Invoke-Command, it won't return the user's information, but yours). Get-Printer is an example amongst many others.
There is no easy, quick way of running commands as the logged on user natively without any third-party apps like PsExec or others so I made this quick function that uses VBS, PS1 and Scheduled Task to make it happen.
It runs completly silently for the user (thanks to the VBS) and the output is shown in your console. Please note it assumes the remote computer has a C:\TEMP.
Created in a Windows 10, powershell v 5.1.17763.503 environement.
I don't pretend it's final and perfect, it's the simplest way I found to do what is needed and I just wanted to share it with you guys as it can be very useful!
Check the comments for explanation of the code and feel free to use it as you wish. Please share your version as I'm curious to see people improve it. A good idea would be to make it support multiple computers, but as I said it's a quick function I did I don't have too much time to put into refining it.
That being said, I had no problems using it multiple times as is :)
*Output returned is in form of a string, if you want to have a proper object, add '| ConvertFrom-String' and play with it :)
PLEASE NOTE: The surefire way of grabbing the username of who is currently logged on is via QWINSTA (since Win32_ComputerSystem - Username is only reliable if a user is logged on LOCALLY, it won't be right if a user is using RDP/RemoteDesktop). So this is what I used to grab the username, however, please note that in our french environement the name of the username property in QWINSTA is "UTILISATEUR",so you have to change that to your needs (english or other language) for it to work. If I remember correctly, it's "USERNAME" in english.
On this line:
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
See code in the answer below.
function RunAsUser {
Param ($ComputerName,$Scriptblock)
#Check that computer is reachable
Write-host "Checking that $ComputerName is online..."
if (!(Test-Connection $ComputerName -Count 1 -Quiet)) {
Write-Host "$ComputerName is offline" -ForegroundColor Red
break
}
#Check that PsRemoting works (test Invoke-Command and if it doesn't work, do 'Enable-PsRemoting' via WMI method).
#*You might have the adjust this one to suit your environement.
#Where I work, WMI is always working, so when PsRemoting isn't, I enable it via WMI first.
Write-host "Checking that PsRemoting is enabled on $ComputerName"
if (!(invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)) {
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList "powershell.exe -command Enable-PSRemoting -SkipNetworkProfileCheck -Force" | Out-Null
do {
Start-Sleep -Milliseconds 200
} until (invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)
}
#Check that a user is logged on the computer
Write-host "Checking that a user is logged on to $ComputerName..."
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
if (!($LoggedOnUser) ) {
Write-Host "No user is logged on to $ComputerName" -ForegroundColor Red
break
}
#Creates a VBS file that will run the scriptblock completly silently (prevents the user from seeing a flashing powershell window)
#"
Dim wshell, PowerShellResult
set wshell = CreateObject("WScript.Shell")
Const WindowStyle = 0
Const WaitOnReturn = True
For Each strArg In WScript.Arguments
arg = arg & " " & strArg
Next 'strArg
PowerShellResult = wshell.run ("PowerShell " & arg & "; exit $LASTEXITCODE", WindowStyle, WaitOnReturn)
WScript.Quit(PowerShellResult)
"# | out-file "\\$ComputerName\C$\TEMP\RAU.vbs" -Encoding ascii -force
#Creates a script file from the specified '-Scriptblock' parameter which will be ran as the logged on user by the scheduled task created below.
#Adds 'Start-Transcript and Stop-Transcript' for logging the output.
$Scriptblock = "Start-Transcript C:\TEMP\RAU.log -force" + $Scriptblock + "Stop-Transcript"
$Scriptblock | out-file "\\$ComputerName\C$\TEMP\RAU.ps1" -Encoding utf8 -force
#On the remote computer, create a scheduled task that runs the .ps1 script silently in the user's context (with the help of the vbs)
Write-host "Running task on $ComputerName..."
Invoke-Command -ComputerName $ComputerName -ArgumentList $LoggedOnUser -ScriptBlock {
param($loggedOnUser)
$SchTaskParameters = #{
TaskName = "RAU"
Description = "-"
Action = (New-ScheduledTaskAction -Execute "wscript.exe" -Argument "C:\temp\RAU.vbs C:\temp\RAU.ps1")
Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd)
RunLevel = "Highest"
User = $LoggedOnUser
Force = $true
}
#Register and Start the task
Register-ScheduledTask #SchTaskParameters | Out-Null
Start-ScheduledTask -TaskName "RAU"
#Wait until the task finishes before continuing
do {
Write-host "Waiting for task to finish..."
$ScheduledTaskState = Get-ScheduledTask -TaskName "RAU" | Select-Object -ExpandProperty state
start-sleep 1
} until ( $ScheduledTaskState -eq "Ready" )
#Delete the task
Unregister-ScheduledTask -TaskName "RAU" -Confirm:$false
}
Write-host "Task completed on $ComputerName"
#Grab the output of the script from the transcript and remove the header (first 19) and footer (last 5)
$RawOutput = Get-Content "\\$ComputerName\C$\temp\RAU.log" | Select-Object -Skip 19
$FinalOutput = $RawOutput[0..($RawOutput.length-5)]
#Shows output
return $FinalOutput
#Delete the output file and script files
Remove-Item "\\$ComputerName\C$\temp\RAU.log" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.vbs" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.ps1" -force
}
#____________________________________________________
#Example command
#Note: Sometimes Start-Transcript doesn't show the output for a certain command, so if you run into empty output, add: ' | out-host' or '| out-default' at the end of the command not showing output.
$Results = RunAsUser -ComputerName COMP123 -Scriptblock {
get-printer | Select-Object name,drivername,portname | Out-host
}
$Results
#If needed, you can turn the output (which is a string for the moment) to a proper powershell object with ' | ConvertFrom-String'
I am trying to figure out how to write a powershell script that will set all .swf extensions to open up on Internet Explorer. I was trying to do this with a command prompt similar to the example below. Unfornately my boss is requiring this to be done through powershell. Any help with this would be greatly appreciated since I have a txt file that will loop through about 400 computers and need to make these changes on.
CMD Way
C:\>ASSOC .swf
.swf=ShockwaveFlash.ShockwaveFlash
C:\>FTYPE ShockwaveFlash.ShockwaveFlash
ShockwaveFlash.ShockwaveFlash="C:\bin\FlashPlayer.exe" %1
What I am Trying:
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
cmd /c assoc .swf=InternetExplorer.Application
### Will the above line automatically install on every pc? ###
}
Any help with trying to insert how to change the FTYPE in powershell so that $computer can cycle through would be greatly appreciated!
ASSOC and FTYPE are CMD.exe built-in commands, not executables, which means they can only be run in the context of CMD. The easiest way to run them is to invoke CMD from PowerShell.
cmd /c assoc .swf
cmd /c ftype ShockwaveFlash.ShockwaveFlash
If you need a "pure" PowerShell implementation, then you need to go to the registry. ASSOC and FTYPE merely write to the registry under theHKEY_CLASSES_ROOT hive. PowerShell does not have a default PSDrive for HKCR:, but that hive is also accessible under HKLM:\Software\Classes.
$ext = '.swf'
$HKCR = 'HKLM:\Software\Classes'
$ftype = Get-ItemProperty -Path "$HKCR\$ext" | select -expand '(default)'
$commandLine = Get-ItemProperty -Path "$HKCR\$ftype\shell\open" | select -expand '(default)'
$commandLine
To update these values, you simply use Set-ItemProperty on the same path.
Set-ItemProperty -Path "$HKCR\$ext" -Name '(default)' -Value 'ShockwaveFlash.ShockwaveFlash'
This requires you to run with Admin privileges. This also assumes that the key already exists. If not, you will have to create it with New-Item
if (-not (Test-Path "$HKCR\$ext")) {
New-Item -Path "$HKCR\$ext"
}
However, if all you want to do is set .swf files to open in iexplore.exe, then retrieving the values is unnecessary, as is modifying the FTYPE key. You need only change the extension association to InternetExplorer.Application instead of ShockwaveFlash.ShockwaveFlash. The following full scripts will do this:
In Batch file:
assoc .swf=InternetExplorer.Application
In PowerShell:
cmd /c assoc .swf=InternetExplorer.Application
In "pure" PowerShell, by modifying the registry:
$key = "HKLM:\Software\Classes\.swf"
$defaultName = '(default)'
$newValue = 'InternetExplorer.Application'
if (-not (Test-Path $key)) {
New-Item -Path $key
}
Set-Itemproperty -Path $key -Name $defaultName -Value $newValue
Note that modifying the registry doesn't take effect immediately. You need to also send a WM_SETTINGCHANGE event, or simply restart explorer.exe (eg: by logging off). You can find code to send the event here, but usually this isn't a problem for automated scripts because they force the user to re-login anyway.
I have an array of Credential objects and I would like to test that these credentials have permissions to write a file to a file share.
I was going to do something like
$myPath = "\\path\to\my\share\test.txt"
foreach ($cred in $credentialList)
{
"Testing" | Out-File -FilePath $myPath -Credential $cred
}
but then I discovered that Out-File doesn't take Credential as a parameter. What's the best way to solve this?
You can use New-PSDrive:
$myPath = "\\path\to\my\share"
foreach ($cred in $credentialList)
{
New-PSDrive Test -PSProvider FileSystem -Root $myPath -Credential $Cred
"Testing" | Out-File -FilePath Test:\test.txt
Remove-PSDrive Test
}
Here is asituation where an old exe (net.exe) seems to do better than powershell...
I guess you could try to map a network drive with the credential provided then test to write a file to that drive :
$cred=get-credential
$pass=$cred.GetNetworkCredential().Password
net use q: \\servername\share $pass /user:$cred.username
Use this script taken from Microsofts TechNet Script Center : http://gallery.technet.microsoft.com/scriptcenter/Lists-all-the-shared-5ebb395a
It is a lot easier to alter to fit your needs then to start completely from scratch.
Open up ListSharedFolderPermissions.ps1, and find the three $Properties vars. add a line at the top of each one so you can tell which user your looking at, so it should now look like this:
$Properties = #{'Username' = $Credential.UserName
'ComputerName' = $ComputerName
. . . . . }
Next, add your new Username property to the select-object line (3 times) :
$Objs|Select-Object Username,ComputerName,ConnectionStatus,SharedFolderName,SecurityPrincipal, `
FileSystemRights,AccessControlType
Once youve added those small pieces in the six appropriate places your script is ready to use:
cd c:\Path\where\you\put\ps1\file
$permissions = #()
$myPath = "computername"
foreach ($cred in $credentialList)
{
$permissions += .\ListAllSharedFolderPermission.ps1 -ComputerName $myPath -Credential $cred
$permissions += " "
}
$permissions | Export-Csv -Path "C:\Permission.csv" -NoTypeInformation
Try using the Invoke-Command function. It will take a credential object and allow you to run an arbitrary script block under that command. You can use that to test out writing the file
Invoke-Command -ScriptBlock { "Testing" | Out-File $myPath } -Credential $cred
I think the Invoke-command approach should work. But if nothing works you can try the powershell impersonation module. It successfully impersonates a user for most Powershell commands without the -Credential switch.
A few ideas:
Create your own PowerShell Provider
Impersonate a user and then write to the share (not sure if possible in powershell)
Use net use d:... as #Kayasax has suggested
Use WScript.Network
I'm very interested in the PowerShell provider myself, but I decided to make something real quick so I went with using the WScript.Network library. I used a hash table to track whether a user would be "authenticated" or not.
$credentials = #() # List of System.Net.NetworkCredential objects
$authLog = #{}
$mappedDrive = 'z:'
$tmpFile = $mappedDrive, '\', [guid]::NewGuid(), '.tmp' -join ''
$path = [io.path]::GetPathRoot('\\server\share\path')
$net = new-object -comObject WScript.Network
foreach ($c in $credentials) {
if ($authLog.ContainsKey($c.UserName)) {
# Skipping because we've already tested this user.
continue
}
try {
if (Test-Path $mappedDrive) {
$net.RemoveNetworkDrive($mappedDrive, 1) # 1 to force
}
# Attempt to map drive and write to it
$net.MapNetworkDrive($mappedDrive, $path, $false, $c.UserName, $c.Password)
out-file $tmpFile -inputObject 'test' -force
# Cleanup
Remove-Item $tmpFile -force
$net.RemoveNetworkDrive($mappedDrive, 1)
# Authenticated.
# We shouldn't have reached this if we failed to mount or write
$authLog.Add($c.UserName, 'Authorized')
}
catch [Exception] {
# Unathenticated
$authLog.Add($c.UserName, 'Unauthorized')
}
}
$authLog
# Output
Name Value
---- -----
desktop01\user01 Authorized
desktop01\user02 Unauthorized
I have the powershell script built and I'm getting a "Random" bit of output into the CSV file. The string is MailboxExport(and a number). It looks like a value that (Get-MailboxExportRequest).name would return but I can't see where I would pull something like that or how it is being inserted. I think I may have just been staring at it too long and I may just need a fresh pair of eyes to spot my mistake. I would go into what the script is trying to do but I've put quite a few notes in the script that should explain it fairly well.
################################################## PST Extraction Script ##################################################
# Completed October 2013 by Trey Nuckolls
#
# This script is meant to extract PST files from the Site 1 Exchange server at the Site2 site and deliver those PST
# files to a share on the Site2 network. The script will change the input CSV file to keep track of which PSTfiles have been
# extracted and when that occoured. The script will also set security on the PST file so only the user and IT administraion
# can access the PST file.
#
# To run this script, enter the username of the Site 1 domain account that you want to target for extraction of a PST file then
# Run the script. Can be run from any machine on the network as long as it is run by someone with domain admin rights on the
# Site 2 network. Powershell v2 or v3 is required to run the script.
#
#############################################################################################################################
$InPstPath = '\\Site1_Server\PST_Store'
$OutPstPath = '\\Site2_Server\PST_Store'
$AdminPath = '\\Site2_Server\PST_Store\Admin\'
#Container for Site1 username
$User = Get-Content $AdminPath'login.txt'
#Container for encrypted Site1 Password
$PWord = Cat $AdminPath'pass.txt' | ConvertTo-SecureString
#Credential package for accessing Site1 resouces
$Credentials = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
#Creation of Powershell Drives for use during session
New-PSDrive -Name Site1Share -PSProvider FileSystem -Root $InPstPath -Credential $Credentials
New-PSDrive -Name Site2Share -PSProvider FileSystem -Root $OutPstPath
#Container for Powershell session to Exchange server
$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://Site1_Server/powershell -Credential $Credentials
#Creation of Powershell session to Site1 Exchange server, including import of exchange commandlets
Import-PSSession $PSSession
#Import of the CSV file that lists users to be targeted
$In_List = Invoke-Command {Import-Csv "\\Site1_Server\PST_Store\To_Be_Exported.csv"} -computername Site1_Server -Credential $Credentials
$Processed = foreach ($objUser in $In_List) {
if ($objUser.Completed -ne "Yes") {
$TargetUser = $objUser.name
$ShortDate = (Get-Date).toshortdatestring()
$SourceFile = "Site1Share:\$TargetUser.pst"
$DestinationFile = "Site2Share:\$TargetUser.pst"
#Export Mailbox to PST File
New-MailboxExportRequest -Mailbox $TargetUser -Filepath $InPstPath\$TargetUser.pst
do {Start-Sleep -Seconds 10}
until((Get-MailboxExportRequest -Status InProgress).count -eq 0)
#Copy PST File to PST Share
Copy-Item -Path $SourceFile -Destination $DestinationFile
#Add Security access on PST file (Target_User-Modify). Domain Admin-Full is inherited from parent.
$Acl = Get-Acl $DestinationFile
$Permission = "Site2_Domain\$TargetUser","Modify","Allow"
$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $Permission
$Acl.SetAccessRule($AccessRule)
$Acl | Set-Acl $DestinationFile
#Remove PST file From Temporary area
Remove-Item -Path $SourceFile -Force
#Write back to checklist for new items that have just been processed
[PSCustomObject]#{Name=$TargetUser;Completed="Yes";Date=$ShortDate}
} else { if ($objUser.Completed -eq "Yes") {
#Passthrough of items that have already been completed
[PSCustomObject]#{Name=$objUser.name;Completed=$objUser.Completed;Date=$objUser.Date}}
}}
#Output the new version of the checklist
$Processed | export-csv -Path C:\TEMP\processed.csv
#Overwrite the old version checklist with the new one
Move-Item -Path C:\TEMP\processed.csv -Destination Site1Share:\To_Be_Exported.csv -force
#Cleanup PsDrives and PsSessions
Remove-PSDrive -Name Site1Share
Remove-PSDrive -Name Site2Share
Remove-PSSession -Session (Get-PSSession)
Input CSV is...
"Name","Completed","Date"
"User1","Yes","10/8/2013"
"User2","Yes","10/11/2013"
"User3",,
and output is...
"Name","Completed","Date"
"User1","Yes","10/8/2013"
"User2","Yes","10/11/2013"
"MailboxExport7",,
"User3","Yes","10/11/2013"
It is indeed very likely that the issue is caused by New-MailboxExportRequest, as you already suspected. The cmdlet prints information about the created object, which lumped together with the rest of the output you create in the loop, and then assigned to the variable $Processed.
To avoid this you can suppress the cmdlet output like this:
New-MailboxExportRequest -Mailbox ... | Out-Null
or like this:
New-MailboxExportRequest -Mailbox ... >$null
Assigning the output to a variable should work as well:
$exportRequest = New-MailboxExportRequest -Mailbox ...
On you Export-CSV, try adding the flag: "-NoTypeInformation"
I think this may be some sort of name space crossover issue between the custom object and another existing object (probably the mailboxexportrequest object on the exchange server). After messing around with this for a while I was able to get it to fail in a new way where the resultant csv file was full of details from the mailbox exports and their was a 'name' column that also had listed the usernames. I changed the hashes on the input csv from 'name to 'username' and the resultant MailboxExport entries have ceased. There are now blank row but I'm certainly willing to live with that imperfection as it doesn't break this (short lived) process.
If anyone has any insight into the root cause I'd certainly love to hear what it is but I think I've figured out a solution to the point that I can live with.
I just burned a couple of hours searching for a solution to send files over an active PSSession. And the result is nada, niente. I'm trying to invoke a command on a remote computer over an active session, which should copy something from a network storage. So, basically this is it:
icm -Session $s {
Copy-Item $networkLocation $PCLocation }
Because of the "second hop" problem, I can't do that directly, and because I'm running win server 2003 I cant enable CredSSP. I could first copy the files to my computer and then send/push them to the remote machine, but how? I tried PModem, but as I saw it can only pull data and not push.
Any help is appreaciated.
This is now possible in PowerShell / WMF 5.0
Copy-Item has -FromSession and -toSession parameters. You can use one of these and pass in a session variable.
eg.
$cs = New-PSSession -ComputerName 169.254.44.14 -Credential (Get-Credential) -Name SQL
Copy-Item Northwind.* -Destination "C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2008R2\MSSQL\DATA\" -ToSession $cs
See more examples at here, or you can checkout the official documentation.
If it was a small file, you could send the contents of the file and the filename as parameters.
$f="the filename"
$c=Get-Content $f
invoke-command -session $s -script {param($filename,$contents) `
set-content -path $filename -value $contents} -argumentlist $f,$c
If the file is too long to fit in whatever the limits for the session are, you could read the file in as chunks, and use a similar technique to append them together in the target location
PowerShell 5+ has built-in support for doing this, described in David's answer.
I faced the same problem a while ago and put together a proof-of-concept for sending files over a PS Remoting session. You'll find the script here:
https://gist.github.com/791112
#requires -version 2.0
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$ComputerName,
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$true)]
[string]
$Destination,
[int]
$TransferChunkSize = 0x10000
)
function Initialize-TempScript ($Path) {
"<# DATA" | Set-Content -Path $Path
}
function Complete-Chunk () {
#"
DATA #>
`$TransferPath = `$Env:TEMP | Join-Path -ChildPath '$TransferId'
`$InData = `$false
`$WriteStream = [IO.File]::OpenWrite(`$TransferPath)
try {
`$WriteStream.Seek(0, 'End') | Out-Null
`$MyInvocation.MyCommand.Definition -split "``n" | ForEach-Object {
if (`$InData) {
`$InData = -not `$_.StartsWith('DATA #>')
if (`$InData) {
`$WriteBuffer = [Convert]::FromBase64String(`$_)
`$WriteStream.Write(`$WriteBuffer, 0, `$WriteBuffer.Length)
}
} else {
`$InData = `$_.StartsWith('<# DATA')
}
}
} finally {
`$WriteStream.Close()
}
"#
}
function Complete-FinalChunk ($Destination) {
#"
`$TransferPath | Move-Item -Destination '$Destination' -Force
"#
}
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$EncodingChunkSize = 57 * 100
if ($EncodingChunkSize % 57 -ne 0) {
throw "EncodingChunkSize must be a multiple of 57"
}
$TransferId = [Guid]::NewGuid().ToString()
$Path = ($Path | Resolve-Path).ProviderPath
$ReadBuffer = New-Object -TypeName byte[] -ArgumentList $EncodingChunkSize
$TempPath = ([IO.Path]::GetTempFileName() | % { $_ | Move-Item -Destination "$_.ps1" -PassThru}).FullName
$Session = New-PSSession -ComputerName $ComputerName
$ReadStream = [IO.File]::OpenRead($Path)
$ChunkCount = 0
Initialize-TempScript -Path $TempPath
try {
do {
$ReadCount = $ReadStream.Read($ReadBuffer, 0, $EncodingChunkSize)
if ($ReadCount -gt 0) {
[Convert]::ToBase64String($ReadBuffer, 0, $ReadCount, 'InsertLineBreaks') |
Add-Content -Path $TempPath
}
$ChunkCount += $ReadCount
if ($ChunkCount -ge $TransferChunkSize -or $ReadCount -eq 0) {
# send
Write-Verbose "Sending chunk $TransferIndex"
Complete-Chunk | Add-Content -Path $TempPath
if ($ReadCount -eq 0) {
Complete-FinalChunk -Destination $Destination | Add-Content -Path $TempPath
Write-Verbose "Sending final chunk"
}
Invoke-Command -Session $Session -FilePath $TempPath
# reset
$ChunkCount = 0
Initialize-TempScript -Path $TempPath
}
} while ($ReadCount -gt 0)
} finally {
if ($ReadStream) { $ReadStream.Close() }
$Session | Remove-PSSession
$TempPath | Remove-Item
}
Some minor changes would allow it to accept a session as a parameter instead of it starting a new one. I found the memory consumption on the Remoting service on the destination computer could grow quite large when transferring large files. I suspect PS Remoting wasn't really designed to be used this way.
NET USE allows you to add a local drive letter for the remote system, which then then allows you to use the drive letter in your PSSession, or even without a PSSession. This is helpful if you don't have Powershell v5.0, and even if you do,
You may use the remote machine name or its IP address as part of the remote UNC path and you can specify the username and password credentials on the same line:
NET USE Z: \\192.168.1.50\ShareName /USER:192.168.1.50\UserName UserPassword
Another example:
NET USE Z: \\RemoteSystem\ShareName /USER:RemoteSystem\UserName UserPassword
OR
NET USE Z: \\RemoteSystem\ShareName /USER:Domain\UserName UserPassword
If you don't supply the user credentials on the same line, you will be prompted for them:
>NET USE Z: \\192.168.1.50\ShareName
Enter the user name for '192.168.1.50': 192.168.1.50\UserName
Enter the password for 192.168.1.50: *****
The command completed successfully.
You may remove the drive letter when you're finished with the following:
NET USE Z: /delete
You can get the full syntax with NET USE /?
>net use /?
The syntax of this command is:
NET USE
[devicename | *] [\\computername\sharename[\volume] [password | *]]
[/USER:[domainname\]username]
[/USER:[dotted domain name\]username]
[/USER:[username#dotted domain name]
[/SMARTCARD]
[/SAVECRED]
[[/DELETE] | [/PERSISTENT:{YES | NO}]]
NET USE {devicename | *} [password | *] /HOME
NET USE [/PERSISTENT:{YES | NO}]
NET is a standard external .exe command in the system folder and works in Powershell just fine.
$data = Get-Content 'C:\file.exe' -Raw
Invoke-Command -ComputerName 'server' -ScriptBlock { $using:data | Set-Content -Path 'D:\filecopy.exe' }
Don't actually know what the maximum file size limitation is.