Remote scheduled Screenshot - powershell

I want to make a scheduled task so I can make a screenshot o a website every hour for monitoring purposes. I am using the following powershell script:
#Open website
start https://nu.nl
#wait
Sleep -Seconds 5
#make screenshot
$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)
We use a automation tool to execute the script but I am getting an error while running it:
Exception calling "CopyFromScreen" with "5" argument(s): "The handle is invalid"
Someone an Idea to resolve this?
When I try this local I have no issue but I'm not able to do it with the automation tool

Related

Random number generator not working after converting to Exe

I have written a dice roller script. I have one with a single die and one with two dice. they both work in powershell, and the single die version works after converting to .exe using ps2exe. but the two die version runs as an exe but I get the following error "You cannot call a method on a null-valued expression"
Below is the 2 die script that gives the error after converting.
<#
.NAME
Random Dice Roller
#>
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Display1 = 1,2,3,4,5,6 | Get-Random
$Display2 = 1,2,3,4,5,6 | Get-Random
$LabelImage = [system.drawing.image]::FromFile("f:\psscripts\Die1.png")
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(400,300)
$Form.text = "Roll The Dice"
$Form.TopMost = $false
$Form.location = New-Object System.Drawing.Point(1,1)
$Form.StartPosition = "CenterScreen"
$Die1 = New-Object system.Windows.Forms.Label
$Die1.Text = "$Display1"
$Die1.AutoSize = $false
$Die1.width = 200
$Die1.height = 200
$Die1.location = New-Object System.Drawing.Point(1,1)
$Die1.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',150,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
$Die1.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#000000")
$Die1.BackgroundImage = $LabelImage
$Die2 = New-Object system.Windows.Forms.Label
$Die2.Text = "$Display2"
$Die2.AutoSize = $false
$Die2.width = 200
$Die2.height = 200
$Die2.location = New-Object System.Drawing.Point(200,1)
$Die2.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',150,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
$Die2.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#000000")
$Die2.BackgroundImage = $LabelImage
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = New-Object System.Drawing.Size(175,220)
$Button1.Size = New-Object System.Drawing.Size(80,50)
$Button1.Text = "Roll Me"
$Button1.Add_Click({Roll})
$Form.controls.AddRange(#($Die1,$Die2,$Button1))
$Die.Add_Click({ Roll })
#region Logic
function Roll{
$Form.Close()
$Form.Dispose()
.\Dice.exe}
#endregion
[void]$Form.ShowDialog()
There are two possible causes here:
Note that there is no safety in wrapping you PowerShell script in an .exe file. In fact the PowerShell script is extracted in a temporary folder and executed from there which might be the cause of your issue (e.g. the relative .\Dice.exe location)
It is unwise to do a $Form.Dispose() from within a form function/event
Remove that from the function and put it outside the form, e.g.:
[void]$Form.ShowDialog(); $Form.Dispose()
(Or do not use the Dispose method at all as PowerShell will usually already takes care of that if you [void] the $From.)
What is your motive for creating an EXE?
An alternative might be to create a CMD file with a Batch bootstrap instead. Create a CMD file with the following code and include your script after this.
<# :
#ECHO OFF
SET f0=%~f0
PowerShell -NoProfile -ExecutionPolicy RemoteSigned -Command ".([scriptblock]::Create((get-content -raw $Env:f0)))"
PAUSE
GOTO :EOF
<#~#>
# Insert PowerShell script after this line.

How to take a remote screenshot with Powershell

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"

i need a powershell script that open urls at specific times, takes screenshot and sends via email

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

How to take screenshot of full page through simple program?

My requirement is to take screenshot of full page in Internet Explorer. I am aware of different tools available online for doing the same but since I want to achieve this on system where I don't have Admin rights, I can do it only with some program which doesn't require any installation.
I am looking for a program which can accept input(site URLs) from excel and then take full page screen shots of those sites.
Till now I only managed to open a site in IE, then do F11 to maximize screen and take screenshot of it. But, that gives cropped page snap since page height is more. I tried this with PowerShell.
Any suggestion/ideas?
Capturing a screenshot
Param(
#[Parameter(Mandatory = $true)][string]
$Path = "C:\Sagar\Screens"
)
$FileName = "$env:COMPUTERNAME - $(get-date -f yyyy-MM-dd_HHmmss).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
$ie = New-Object -com internetexplorer.application
$ie.Visible = $true
$ie.Navigate2("https://www.google.com/webhp?sourceid=chrome- instant&ion=1&espv=2&ie=UTF-8#q=how%20to%20send%20keys%20through%20powershell")
while($ie.busy){
Sleep -Seconds 2
}
Sleep -Seconds 2
$sig = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
Add-Type -MemberDefinition $sig -name NativeMethods -namespace Win32
# Minimize window
[Win32.NativeMethods]::ShowWindowAsync($ie.HWND, 2)
# Restore window
[Win32.NativeMethods]::ShowWindowAsync($ie.HWND, 3)
#$ie.FullScreen = $true
Sleep -seconds 3
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[System.Windows.Forms.SendKeys]::SendWait("{F11}")
sleep -Seconds 2
Add-Type -AssemblyName System.Drawing
# Create bitmap using the top-left and bottom-right bounds
$bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $ie.Width, $ie.Height
# Create Graphics object
$graphic = [System.Drawing.Graphics]::FromImage($bitmap)
$ie.Left
$ie.Top
# Capture screen
$graphic.CopyFromScreen($ie.Left, $ie.Top, 0, 0, $bitmap.Size)
# Save to file
$bitmap.Save($File)
Write-Output "Screenshot saved to:"
Write-Output $File
#
Found a solution to this. There is this thing called 'Watin' which helps in automation using IE and Firefox. It has a method to capture full page screenshot. Though screenshot quality is average, Watin works perfectly.
$executingScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$filePath = $executingScriptDirectory + "\WatiN-2.1.0.1196\bin\net40\WatiN.Core.dll" #$executingScriptDirectory + "\net20\WatiN.Core.dll"
$filePath1 = $executingScriptDirectory + "\WatiN-2.1.0.1196\bin\net40\Interop.SHDocVw.dll"
[System.Reflection.Assembly]::LoadFile($filePath);
[System.Reflection.Assembly]::LoadFile($filePath1);
$url = "https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=how%20to%20send%20keys%20through%20powershell"
$ie = new-object WatiN.Core.IE $URL
while($ie.busy){
sleep -milliseconds 50
}
$ie.BringToFront()
$ie.CaptureWebPageToFile("C:\Sagar\Scripts\IsIt.jpg");

How to Open; SaveAs; then Close an Excel 2013 (macro-enabled) workbook from PowerShell4

Doing a search on the above Com operations yields links dating to '09 and even earlier. Perhaps it hasn't changed but I find myself bumping up against errors where 'it is being used by another process.' - even though no Excel app is open on my desktop. I have to reboot to resume.
To be clear - I'm trying to open an existing file; immediately SaveAs() (that much works), add a sheet, Save(); Close() - and then, importantly, repeat that cycle. In effect, I'm creating a few dozen new sheets within a loop that executes the above 'Open Master; SaveAs(); Edit Stuff; Save; Close;
From the examples I've seen this is not a typical workflow for PowerShell. Pasted at the very bottom is my provisional script - pretty rough and incomplete but things are opening what they need to open and adding sheet also works - until I know I have the right way to cleanly close stuff out I'm not worried about the iterations.
I've found a couple examples that address closing:
From http://theolddogscriptingblog.wordpress.com/2012/06/07/get-rid-of-the-excel-com-object-once-and-for-all/
$x = New-Object -com Excel.Application
$x.Visible = $True
Start-Sleep 5
$x.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($x)
Remove-Variable x
And from http://social.technet.microsoft.com/Forums/windowsserver/en-US/24e57b61-e792-40c1-8aff-b0a8205f48ab/updated-opened-excel-using-powershell?forum=winserverpowershell
Set-ItemProperty $path -name IsReadOnly -value $false
$Excel.ActiveWorkBook.Save()
$openfile.Close()
$openfile = $null
$Excel.Quit()
$Excel = $null
[GC]::Collect()
<>
function MakeNewBook($theWeek, $AffID){
$ExcelFile = "C:\csv\InvoiceTemplate.xlsm"
$Excel = New-Object -Com Excel.Application
$Excel.Visible = $True
$Workbook = $Excel.Workbooks.Open($ExcelFile)
$theWeek = $theWeek -replace "C:\\csv\\", ""
$theWeek = $theWeek -replace "\.csv", ""
$theWeek = "c:\csv\Invoices\" +$AffID +"_" + $theWeek + ".xlsm"
$SummaryWorksheet = $Workbook.worksheets.Item(1)
$Workbook.SaveAs($theWeek)
return $Excel
}
function MakeNewSheet($myBook, $ClassID){
$SheetName = "w"+$ClassID
#$Excel = New-Object -Com Excel.Application
#$Excel.Visible = $True
$wSheet = $myBook.WorkSheets.Add()
}
function SaveSheet ($myExcel)
{
#$WorkBook.EntireColumn.AutoFit()
#Set-ItemProperty $path -name IsReadOnly -value $false
$myExcel.ActiveWorkBook.Save()
$openfile= $myExcel.ActiveWorkBook
$openfile.Close()
$openfile = $null
$myExcel.Quit()
$myExcel = $null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($myExcel)
Remove-Variable $myExcel
[GC]::Collect()
}
$theWeek = "C:\csv\wkStart2013-11-04.csv"
$x = Import-Csv $theWeek
foreach ($xLine in $x){
if ($x[0]){
$AffID = $x[1].idAffiliate
$myExcel = MakeNewBook $theWeek $x[1].idAffiliate
$ClassID = $x[1].idClass
MakeNewSheet $myExcel $ClassID
continue
}
SaveSheet($myExcel)
$AffID = $_.$AffID
$wID = $xLine.idClass
#MakeNewSheet($wID)
Echo "$wID"
}
As a follow up after playing around with this issue myself. I geared my solution around Ron Thompson's comment minus the function calls:
# collect excel process ids before and after new excel background process is started
$priorExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
$Excel = New-Object -ComObject Excel.Application
$postExcelProcesses = Get-Process -name "*Excel*" | % { $_.Id }
# your code here (probably looping through the Excel document in some way
# try to gently quit
$Excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
# otherwise allow stop-process to clean up
$postExcelProcesses | ? { $priorExcelProcesses -eq $null -or $priorExcelProcesses -notcontains $_ } | % { Stop-Process -Id $_ }
My experience has been that the Quit method doesn't work well, especially when looping. When you get the error, instead of rebooting, open up Task Manager and look at the Processes tab. I'm willing to bet you'll see Excel still open -- maybe even multiple instances of it. I solved this problem by using Get-Process to find all instances of Excel and piping them to Stop-Process. Doesn't seem like that should be necessary, but it did the trick for me.
You should not have to keep track of processes and kill them off.
My experience has been that to properly and completely close Excel (including in loops), you also need to release COM references. In my own testing have found removing the variable for Excel also ensures no remaining references exist which will keep Excel.exe open (like if you are debugging in the ISE).
Without performing the above, if you look in Task Manager, you may see Excel still running...in some cases, many copies.
This has to do with how the COM object is wrapped in a “runtime callable wrapper".
Here is the skeleton code that should be used:
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$workbook = $excel.Workbooks.Add()
# or $workbook = $excel.Workbooks.Open($xlsxPath)
# do work with Excel...
$workbook.SaveAs($xlsxPath)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
# no $ needed on variable name in Remove-Variable call
Remove-Variable excel
Try this
$filePath = "E:\TSMBackup\TSMDATAPull\ODCLXTSM01_VM.xlsx"
$excelObj = New-Object -ComObject Excel.Application
$excelObj.Visible = $true
$workBook = $excelObj.Workbooks.Open($filePath)
$workSheet = $workBook.Sheets.Item("Sheet1")
$workSheet.Select()
$workBook.RefreshAll()
$workBook.Save()
$excelObj.Quit()