I'm trying to figure out how to take remote screenshots over PowerShell from an administrator's account on the AD server to any computer on the network.
So far I've got the following.
$ComputerName = '<THECOMPUTER>'
copy-item "C:\Public\Software\Take-Screenshot.ps1" "\\$ComputerName\C$\"
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
powershell -nop -c "C:\Take-Screenshot.ps1"
}
Take-Screenshot.ps1 is in from here, but I've added the following to the bottom of the script to actually run the function.
Take-ScreenShot -screen -file C:\s.png -imagetype png
After the screenshot is taken, I'll copy it back to the host, but the problem is the picture is completely black.
I'm thinking this might be because the powershell is running the program, but there's not session attached to it, so there really is not screen??
So I got this to work but it is a little involved. Works with multiple monitors.
You will need Screenshot.ps1 on the remote PC, your trigger script and PSExec on local PC (Google).
# This is Screenshot.ps1
# Add types and variables
$File = "C:\Temp\Screenshot1.bmp"
Add-Type -AssemblyName System.Windows.Forms
Add-type -AssemblyName System.Drawing
# Gather Screen resolution information
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
$Width = $Screen.Width
$Height = $Screen.Height
$Left = $Screen.Left
$Top = $Screen.Top
# Set bounds
$bitmap = New-Object System.Drawing.Bitmap $Width, $Height
# Create Object
$graphic = [System.Drawing.Graphics]::FromImage($bitmap)
# Capture
$graphic.CopyFromScreen($Left, $Top, 0, 0, $bitmap.Size)
# Save
$bitmap.Save($File)
And then for the trigger script
#Setup Variables
$ComputerName = "ComputerName"
$PSExec = "C:\temp\tools\psexec.exe"
# Captures session details
$quser = (((query user /server:$ComputerName) -replace '^>', '') -replace '\s{2,}', ',' | ConvertFrom-Csv)
# Takes screenshot of remote PC
&$PSExec -s -i $quser.ID "\\$ComputerName\" PowerShell -WindowStyle Hidden -File "C:\Temp\screenshot.ps1"
Related
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/
This is my script:
$name = function Get-Name {
$env:computername
}
Get-Name
$time = function Get-SystemUptime {
$operatingSystem = Get-WmiObject Win32_OperatingSystem
"$((Get-Date) - ([Management.ManagementDateTimeConverter]::ToDateTime($operatingSystem.LastBootUpTime)))"
}
Get-SystemUptime
$string = Get-SystemUptime
$separator = "\."
$string.Split($separator, 2)
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Add-Type -AssemblyName PresentationFramework
Add-Type –AssemblyName System.Windows.Forms
$days = $string.Split(".")[0]
if ([convert]::ToInt32($days, 10) -ge 1) {
$MsgboxTxt = "TXT $($days) TXT"
$MsgboxTitle = '!!! TXT !!!'
[System.Windows.Forms.MessageBox]::Show($MsgboxTxt, $MsgboxTitle, [Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Stop, [System.Windows.Forms.MessageBoxDefaultButton]::Button1, [System.Windows.Forms.MessageBoxOptions]::DefaultDesktopOnly)
}
I start it with this command on remote computer by my company program who runs it with batch (CMD) administration rights:
Powershell.exe -ExecutionPolicy RemoteSigned -File uptime.ps1
When I run in on local computer by copy file and run it from CMD in local priveliges it's OK. Everything working fine.
I think my company program (LOGSystem) runs it on administration user, not logged user.
Is there a option to run it on logged users using CMD command or else?
I need the script to run at specific times, gets the system time to determine which url to open, takes the screenshot, saves it and send it via mail.
If i break the code down portions of it run but when i include the time checks and if statement it doesn't run.
Here is the script i came up with but its not running.
Function Get-shot {
$now = (Get-Date -Format "hh:mm:ss")
$ie=new-object -com "InternetExplorer.Application"
$ie.Visible=$true
$path="C:\checks"
Function takeshot {
$ScreenBounds = [Windows.Forms.SystemInformation]::VirtualScreen
$ScreenshotObject = New-Object Drawing.Bitmap
$ScreenBounds.Width,
$ScreenBounds.Height
$DrawingGraphics = [Drawing.Graphics]::FromImage($ScreenshotObject)
$DrawingGraphics.CopyFromScreen($ScreenBounds.Location, [Drawing.Point]::Empty, $ScreenBounds.Size)
$DrawingGraphics.Dispose()
$ScreenshotObject.Save($FilePath)
$ScreenshotObject.Dispose()
}
Try {
Add-Type -Assembly System.Windows.Forms
If ($now -eq "08:28:00")
{
<#opens internet explorer and navigates to the page#>
$ie.navigate("www.google.com")
$Time = (Get-Date)
[string] $FileName = "$($Time.Month)"
$FileName += '-'
$FileName += "$($Time.Day)"
$FileName += '-'
$FileName += "$($Time.Year)"
$FileName += '-'
$FileName += '.png'
[string] $FilePath = (Join-Path $Path $FileName)
#takes the screenshot
DO {
takeshot
Write-Verbose "Saved screenshot to $FilePath."
} while ($now -eq "08:28:30")
}
} Catch {
Write-Warning "$Error[0].ToString() +
$Error[0].InvocationInfo.PositionMessage"
}
}
You don't state where this will be run. Meaning:
On a server / workstation you control, and are interactively logged
on to.
On a server / workstation you control and you are not interactively
logged on to.
On a server / workstation you control, that someone else is logged
on to.
You don't say if this is to be a scheduled task or an event monitor.
You don't say or show how the target URL is determined. So, is it
embedded, pulled from some list, or user input.
You are calling the bitmap type but specifying .png extension.
You don't handle multi-monitor in your code.
There are several things that are not correct in your posted code.
File paths do not match up. You have $path is some place
The nested function declaration is not really needed based on what you are after.
The way you are calling the object for the bitmap is not correct, as it will generate errors, which you don't show in your code.
WARNING: A constructor was not found. Cannot find an appropriate
constructor for type Drawing.Bitmap. A constructor was not found.
Cannot find an appropriate constructor for type Drawing.Bitmap. You
cannot call a method on a null-valued expression. You cannot call a
method on a null-valued expression. Exception calling "FromImage" with
"1" argument(s): "Value cannot be null.
I am not sure why the need for the Do/While, since you need only to
call the take shot code once.
Your code will not start at any predetermined time, unless you have
a scheduled task or some other event trigger to start it.
An for this...
but when i include the time checks and if statement it doesn't run.
Your code assigns a definitive time.
If ($now -eq "08:28:00")
If it is not that exact time, when you run the code, the code will not run, because you told it not to, if it is not 08:28:00".
If you are running this throughout the day for your testing. Change the above to this...
If ($now -ge "08:28:00")
Yet, again, this does not really make since. As I stated earlier, unless this code is on some type of trigger, WMI event, Scheduled Task, already running in memory via a infinite loop (not a good thing BTW, well, you could set a timed cycled one), then it is not going to fire anyway.
Walkthrough (you'll have to wrap your logic around this example - including any multi-monitor stuff)
Clear-Host
$StartTime = $null
$TargetUrl = $null
$FileName = $null
$File = $null
# script to run at specific times
## Set up a schedueld task for the script run cycle(s)
### Scheduled Task (set script run daily at "08:28:00") )
### WMI Event Monitor (check for clock time of "08:28:00"))
### Load the script on boot up and use a timed infinite loop (Load at boot, Loop un time time of "08:28:00" - need to reload script each day to handle if use never reboots)
# determine which url to open
# How the URL is determined
$TargetUrl = "google.com"
# Get-Content -Path "D:\Temp\UrlList.txt" | Get-Random -Count 1
# $ResponseUrl = (Read-Host -Prompt "Enter the URL to use")
# Open the IE to the needed URL
$ie = new-object -com "InternetExplorer.Application"
$ie.navigate($TargetUrl)
$ie.visible = $true
$ie.fullscreen = $False
While ($ie.Busy) {Start-Sleep -Milliseconds 100}
# take the screenshot and saves it
Function New-ScreenShot
{
[CmdletBinding()]
[Alias('nss')]
Param
(
# [Parameter(Mandatory = $true)]
[string]$Path = "$env:USERPROFILE\Pictures"
)
$FileName = "$($env:COMPUTERNAME)_$(get-date -f yyyyMMddHHmmss).bmp"
$File = "$Path\$FileName"
Add-Type -AssemblyName System.Windows.Forms
Add-type -AssemblyName System.Drawing
# Gather Screen resolution information
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
$Width = $Screen.Width
$Height = $Screen.Height
$Left = $Screen.Left
$Top = $Screen.Top
# Create bitmap using the top-left and bottom-right bounds
$bitmap = New-Object System.Drawing.Bitmap $Width, $Height
# Create Graphics object
$graphic = [System.Drawing.Graphics]::FromImage($bitmap)
# Capture screen
$graphic.CopyFromScreen($Left, $Top, 0, 0, $bitmap.Size)
# Save to file
$bitmap.Save($File)
Write-Output "Screenshot saved to:"
Write-Output $File
Start-Sleep -Seconds 1
Start-Process -FilePath mspaint -ArgumentList $File
}
New-ScreenShot
# send it via mail
# from the PoSH built-in help file
# Example 2: Send an attachment
Send-MailMessage -From "User01 <user01#example.com>" `
-To "User02 <user02#example.com>", "User03 <user03#example.com>" `
-Subject "Sending the URL Screenshot Attachment" `
-Body "Forgot to send the attachment. Sending now." `
-Attachments $File -Priority Low -dno onSuccess, onFailure -SmtpServer "smtp.example.com"
Update
Relative to the the OP comments
– athanas otieno
Sure you can do different types.
What I provided was the basics to do this. There are far more elegant options.
There are plenty of other samples to be leveraged that show .png or .jpg use case. These are all over the web and even on this very site. See these posts.
How can I do a screen capture in Windows PowerShell?
Get-Screenshot.ps1
<#
.Synopsis
Gets a screen capture
.Description
Captures the current screen
.Example
# Capture the whole screen
Get-ScreenShot
.Example
# Capture the current window
Get-ScreenShot -OfWindow
.Example
# Capture a set of coordinates
Get-ScreenShot -OfCoordinates 320, 240
.Link
http://stackoverflow.com/questions/2969321/how-can-i-do-a-screen-capture-in-windows-powershell
#>
# The image format used to store the screen capture
[Parameter(ValueFromPipelineByPropertyName=$true)]
[ValidateSet('PNG', 'JPEG', 'TIFF', 'GIF', 'BMP')]
[string]
$ImageFormat = 'JPEG', https://www.powershellgallery.com/packages/RoughDraft/0.1/Content/Get-Screenshot.ps1
Take-Screenshot This script has a function that allows you to take a
screenshot of the entire desktop or of an active window. Also includes
option to save the screenshot to a file.
Take-ScreenShot -screen -file "C:\image.png" -imagetype png
https://gallery.technet.microsoft.com/scriptcenter/eeff544a-f690-4f6b-a586-11eea6fc5eb8/view/Discussions
Even this script appears to be where you got yours from, but I could be wrong, as often it's really easy to see folks to end up similar things. It has happen to me many times. Of course forking / borrowing and tweaking is a thing.
Changing a screenshot to JPG instead of PNG
I've got a script that works that runs on a scheduled task to snapshot
the screen of a computer. Basically the computer runs a query at a
regular interval that is pretty resource intensive on the server but
has information that anyone might want to see at a given time.
https://social.technet.microsoft.com/Forums/windowsserver/en-US/c121b827-44c8-49ac-83c6-356fa720e169/changing-a-screenshot-to-jpg-instead-of-png?forum=winserverpowershell
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