Alternative to time delay while installing exe through PowerShell? - powershell

I have a software exe which I am trying to install via PowerShell. It's working fine. I am using SendKeys to navigate through the installation GUI. I have given delay between two SendKeys commands, because software takes some time between two steps, but that installation time varies from computer to computer.
My question is how can I bypass this time delay dependency in SendKeys? I have tried AppActivate but its of no use for me. Is there any alternative to delay?

Sure.
I've converted Nitesh's C# function to a Powershell script
$signature_user32_GetForegroundWindow = #"
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
"#
$signature_user32_GetWindowText = #"
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
"#
$foo = `
Add-Type -MemberDefinition $signature_user32_GetForegroundWindow `
-Name 'user32_GetForegroundWindow' `
-Namespace 'Win32' `
-PassThru
$bar = `
Add-Type -MemberDefinition $signature_user32_GetWindowText `
-Name 'user32_GetWindowText' `
-Namespace 'Win32' `
-Using System.Text `
-PassThru
[int]$nChars = 256
[System.IntPtr] $handle = New-object 'System.IntPtr'
[System.Text.StringBuilder] $Buff = New-Object 'System.Text.StringBuilder' `
-ArgumentList $nChars
$handle = $foo::GetForegroundWindow()
$title_character_count = $bar::GetWindowText($handle, $Buff, $nChars)
If ($title_character_count -gt 0) { Write-Output $Buff.ToString() }
There is a lot going on here. Lemme explain a little of what I did.
I've created two method signatures (the bit in the here-string); one for each function we're calling.
I use those signatures to create corresponding types. Again, one for each method.
For the GetWindowType (which passes the title back in a string and needs a reference to System.Text), I pass in the System.Text namespace in the -Using parameter.
Behind the scenes, PowerShell adds references to the System and System.Runtime.InteropServices so no need to worry about those.
I create my string size ($nChars), window pointer ($handle), and window title buffer ($Buff)
I call the functions through the type-pointer: $foo... and $bar...
Here is what I get when I run all this...
Whenever I have to call the Windows API (which isn't really my thing), I reference the following two articles:
PowerShell P/Invoke Walkthrough
Use PowerShell to Interact with the Windows API: Part 1
I hope this helps!

Related

Empty recycle bin with Powershell V2

Is there a way to empty the recycle bin using Powershell 2.0.
I do not want to update Powershell.
You could clear recycle bin via com object. Like so:
$Shell= New-Object -ComObject Shell.Application
$Bin = $Shell.NameSpace(10)
foreach ($Item in #($Bin.Items())){Remove-item $Item.Path -Force}
You could also directly call SHEmptyRecycleBin Win32 function:
$definition = #'
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
public static extern uint SHEmptyRecycleBin(IntPtr hwnd, string pszRootPath, uint dwFlags);
'#
$winApi = Add-Type -MemberDefinition $definition -Name WinAPI -Namespace Extern -PassThru
$winApi::SHEmptyRecycleBin(0, $null, 7)
All recycle bins are deleted, no confirmation message is shown, no progress bar, no sound.

Powershell to run PostMessage Windows API function failing

I need to write a script to install Java runtime of a specific version, add to the system path variable then go on to run the Java program. I want to do this all in one go without having to restart the target computer.
I found this so question and thought I could try to change to my needs.
SendMessage is causing script to hang
So I tried with PostMessage as per the answer. Obviously I needed to change the message.
So I tried code like this:
echo Start of experiment
$NewPath = (Get-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Session Manager\Environment" -Name Path).Path
$NewPath = "%JAVA_BIN%;" + $NewPath
$RegKey ="HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
Set-ItemProperty -Path $RegKey -Name Path -Value $NewPath
echo Updated Path environment variable
# Next bit which isn't working
$HWND_BROADCAST = 0xffff
$WM_SETTINGCHANGE = 0x001A
$ENVIRON = "Environment"
#Store the C# signature of the PostMessage function.
$signature = #"
[DllImport("user32.dll")]
public static extern int PostMessage(int hWnd, int hMsg, int wParam, int lParam);
"#
#Add the SendMessage function as a static method of a class
$PostMessage = Add-Type -MemberDefinition $signature -Name "Win32PostMessage" -Namespace Win32Functions -PassThru
#Invoke the PostMessage Function
$PostMessage::PostMessage($HWND_BROADCAST, $WM_SETTINGCHANGE, 0, $ENVIRON)
echo end of experiment
This is my output:
Start
of
experiment
Cannot convert argument "3", with value: "Environment", for "PostMessage" to type "System.Int32": "Cannot convert value "Environment" to type "System.Int32".
Error: "Input string was not in a correct format.""
At C:\share\tvm_drivers\PED\setenv.ps1:43 char:26
+ $PostMessage::PostMessage <<<< ($HWND_BROADCAST, $WM_SETTINGCHANGE, 0, $ENVIRON)
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
end
of
experiment
I am running this on Windows 7.
Can anyone help?
For a bit of background, if you reboot the target PC after setting the path env variable, then the setting works. But if you don't reboot, it is necessary to send a broadcast message as above to tell any other Window, including the command prompt, about the change. That is why I need the message.
This is the powershell that successfully updates all the open windows about the env change.
# Notifies other processes that the global environment block has changed.
# This lets other processes see changes to ENV: without having to reboot
# or logoff/logon. A non-zero result from SendMessageTimeout indicates success.
if (-not ("win32.nativemethods" -as [type])) {
# import sendmessagetimeout from win32
add-type -Namespace Win32 -Name NativeMethods -MemberDefinition #"
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"#
}
$HWND_BROADCAST = [intptr]0xffff;
$WM_SETTINGCHANGE = 0x1a;
$result = [uintptr]::zero
# notify all windows of environment block change
[win32.nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,
[uintptr]::Zero, "Environment", 2, 5000, [ref]$result);

Remotely locking a workstation is not working, why?

So about a year ago, I discovered this PowerShell script:
Function Lock-Workstation
{
param(
$Computername,
$Credential
)
if(!(get-module taskscheduler)){Import-Module TaskScheduler}
New-task -ComputerName $Computername -credential:$Credential |
Add-TaskTrigger -In (New-TimeSpan -Seconds 30) |
Add-TaskAction -Script `
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature `
-name "Win32LockWorkStation" `
-namespace Win32Functions `
-passthru
$LockWorkStation::LockWorkStation() | Out-Null
} | Register-ScheduledTask TestTask -ComputerName $Computername `
-credential:$Credential
}
I cannot get it working. I get all kinds of weird errors. It starts with errors about not being able to find the terminating "#, and once I get that fixed it starts throwing errors in the TaskScheduler module, specifically that it cannot load the type [__ComObject] (which is used in a couple of the TaskScheduler scripts, and I can't find any documentation on it).
I am trying to get this working in PowerShell v2.
Anyone got any ideas?
EDIT 1:
So I've done some more testing, and it appears that technically it is working (there was a typo in the TaskScheduler module that was causing it to fail completely), but despite the task being scheduled on the remote workstation, the execution of that task fails, kinda. Frequently the task will "run" but with no results, despite having the credentials of the currently logged on user.
EDIT 2:
Downvotes? Really? I'm having issues, I've described the errors I am getting, and rather than offer a suggestion you downvote the question? What is this, Reddit?
can't test your code at the moment but you may want to use a simpler approach (not tested):
... | Add-TaskAction -Script { rundll32.exe user32.dll,LockWorkStation }

Can this be made to work on a remote computer?

I've been killing myself trying to get this to work on a remote computer, is it even possible? If so, can someone point me in the right direction?
Here's the code:
Function Lock-WorkStation {
#Requires -Version 2.0
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
}
I can't test here, but for me it can NOT work because, as you can read in Microsoft documentation, the LockWorkStation function is callable only by processes running on the interactive desktop. In addition, the user must be logged on.
So when you connect to a remote computer using PSSession as far as I understand you are not in the interactive session.
Nothing to do with this, but it can help in Windows Vista/7 2008/R2, you can use the command tsdiscon.exe to lock a Remote Desktop session or your workstation.
Here is a sample where, logged as adminstrator domain on my computer, I first list, then lock the console session on my server.
PS> query session /server:WM2008R2ENT
SESSION UTILISATEUR ID ÉTAT TYPE PÉRIPHÉRIQUE
services 0 Déco
console jpb 2 Actif
PS> tsdiscon 2 /server:WM2008R2ENT
It's possible. But you need a workaround to connect to the interactive session.
Download the PowerShellPack and install it. You only need one module called "TaskScheduler".
I've tested the following code:
Function Lock-Workstation
{
param(
$Computername,
$Credential
)
if(!(get-module taskscheduler)){Import-Module TaskScheduler}
New-task -ComputerName $Computername -credential:$Credential |
Add-TaskTrigger -In (New-TimeSpan -Seconds 30) |
Add-TaskAction -Script `
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature `
-name "Win32LockWorkStation" `
-namespace Win32Functions `
-passthru
$LockWorkStation::LockWorkStation() | Out-Null
} | Register-ScheduledTask TestTask -ComputerName $Computername `
-credential:$Credential
}
You can use it like this:
Lock-Workstation "NameOfTheComputer" (Get-Credential)
or like this:
Lock-Workstation "NameOfTheComputer"
If you receive an error in Connect-ToTaskScheduler when specifying a credential, it's because there is a typo in the module (edit Connect-ToTaskScheduler.ps1 and replace "$NetworkCredentail.Domain," with "$NetworkCredential.Domain,"

Get PID of COM server

I create a com object in powershell like so:
$application = new-object -ComObject "word.application"
Is there a way to get the PID (or some other unique identifier) of the launched MS Word instance?
I want to check if the program is blocked e.g. by modal dialogs asking for passwords, and I can't do it from whithin PowerShell.
Ok, I found out how to do it, we need to call the Windows API. The trick is to get the HWND, which is exposed in Excel and Powerpoint, but not in Word. The only way to get it is to change the name of the application window to something unique and find it using "FindWindow". Then, we can get the PID using the "GetWindowThreadProcessId" function:
Add-Type -TypeDefinition #"
using System;
using System.Runtime.InteropServices;
public static class Win32Api
{
[System.Runtime.InteropServices.DllImportAttribute( "User32.dll", EntryPoint = "GetWindowThreadProcessId" )]
public static extern int GetWindowThreadProcessId ( [System.Runtime.InteropServices.InAttribute()] System.IntPtr hWnd, out int lpdwProcessId );
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
"#
$application = new-object -ComObject "word.application"
# word does not expose its HWND, so get it this way
$caption = [guid]::NewGuid()
$application.Caption = $caption
$HWND = [Win32Api]::FindWindow( "OpusApp", $caption )
# print pid
$myPid = [IntPtr]::Zero
[Win32Api]::GetWindowThreadProcessId( $HWND, [ref] $myPid );
"PID=" + $myPid | write-host
you may be able to use
get-process -InputObject <Process[]>