Powershell automated testing code freezes when pop up window appears in GUI. - powershell

I'm using Jenkins to perform some automated web testing on a web application.
I'm using powershell to automatically fill out forms, return values, etc. But sometimes a popup window will appear mid test. Weather its an error message or a sub form that needs to be filled out.
When this happens though, my code freezes indefinitely and I need to either exit out of the build or manually close the pop up window myself (defeating the purpose of automated testing).
Ideally I would like to be able to select the open pop up window as an object and fill out forms/ click buttons on it, but my code just freezes and doesn't give me chance.
Would anyone have any advice on how to overcome this?

I don't know Jenkins but here is a PowerShell function that shows a Messagebox that will disapear after a timeout interval specified in milli seconds. To this popup won't be blocking.
function Show-Messagebox
{
param([String]$Title, [String]$Message, [Int]$TimeOut=2000)
$TypeDef = #'
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class Win32API
{
private const UInt32 WM_CLOSE = 0x0010;
[DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)]
private static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.Dll")]
private static extern int PostMessage(IntPtr hWnd, UInt32 msg, int wParam, int lParam);
public static void ShowMessageBox(string Message, string Caption, int TimeOut = 2000)
{
var timer = new System.Timers.Timer(TimeOut) { AutoReset = false };
timer.Elapsed += delegate
{
IntPtr hWnd = FindWindowByCaption(IntPtr.Zero, Caption);
if (hWnd.ToInt32() != 0) PostMessage(hWnd, WM_CLOSE, 0, 0);
};
timer.Enabled = true;
MessageBox.Show(Message, Caption);
}
}
'#
Add-Type -TypeDefinition $TypeDef -ReferencedAssemblies System.Windows.Forms
[Win32API]::ShowMessageBox($Message, $Title, $TimeOut)
}
The function can be called like so:
Show-Messagebox -Title "The Title" -Message "The complete message"
It's a little clumsy having to put the whole type definition of a class in C# code as a here string into the function (there a no white spaces allowed before the end mark '# of the here string) but it's still a simple copy&paste solution.
If you replace the [System.Windows.Forms.MessageBox]::Show()-calls in a PoweShell script with this function there should be no blocking Messageboxes any more.

Related

Powershell always-on-top script

I tried this script :
https://github.com/bkfarnsworth/Always-On-Top-PS-Script
It listed all apps successfully, but I doens't seem to actually make a window on top (I selected a notepad window).
FYI there exists a standard Always on Top utility in Microsoft PowerToys that can do this.
Anyways, if you need to automate this, you can do this from PowerShell using the SetWindowPos function (winuser.h) where the second argument (-1) stands for TOPMOST:
$User32 = Add-Type -Debug:$False -MemberDefinition '
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,int Y, int cx, int cy, uint uFlags);
' -Name "User32Functions" -namespace User32Functions -PassThru
$Handle = (Get-Process -Name notepad).MainWindowHandle
[Void]$User32::SetWindowPos($Handle, -1, 0, 0, 0, 0, 0x53)

Manipulate Afx OLE Control in External Window

I have to manipulate (including get and set text from) an accounting system using Powershell.
The process has a window which contain a grid-like container (the one with blue background colour in the screenshot).
The container contains some Text fields and Combo-boxes.
I tried different ways to get the UI controls(The Text fields/'Edit' Controls in the container).
I tried UIAutomation as follow but fail to found any Text field/'Edit' Control but found the Comobo-box.
Add-Type -AssemblyName UIAutomationClient
$ele=[System.Windows.Automation.AutomationElement]::FromHandle($hwnd)
$elecol=$ele.FindAll([System.Windows.Automation.TreeScope]::Descendants,
[System.Windows.Automation.Condition]::TrueCondition)
for ($i=0;$i -lt $elecol.count;$i++){$elecol[$i].Current} #Only found Scroll Bars & Combo
If look into the "AutomationElement.Current" of container itself ($ele.Current), it looks like the following.
ControlType : System.Windows.Automation.ControlType
LocalizedControlType : pane
Name :
AcceleratorKey :
AccessKey :
HasKeyboardFocus : False
IsKeyboardFocusable : False
IsEnabled : True
BoundingRectangle : 350,1658,2830,443
HelpText :
IsControlElement : True
IsContentElement : True
LabeledBy :
AutomationId :
ItemType :
IsPassword : False
ClassName : AfxGWOleControl42
NativeWindowHandle : 3409268
ProcessId : 11656
IsOffscreen : False
Orientation : None
FrameworkId : Win32
IsRequiredForForm : False
ItemStatus :
And if looks into the supported Patterns ($ele.GetSupportedPatterns()) of the container, it shows nothing.
I also tried using WinAPI to Enum All Child Windows but found No Text field/'Edit' Control too.
function Enum-ChildWindows{
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
[intPtr]$Hwnd
)
BEGIN{
if (("EnumChildWindowsAPI" -as [type]) -eq $null){
Add-Type #"
using System; // namespace "IntPtr"
using System.Runtime.InteropServices; // namespace "DLLImport"
using System.Collections.Generic; // namespace "List"
public class EnumChildWindowsAPI
{
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc,GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
return true;
}
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
}
"#
}
}
PROCESS{
return [EnumChildWindowsAPI]::GetChildWindows($Hwnd)
}
}
And of course, sending WM_SETTEXT message to the container is also not working.
Watching into the Spy++, the class name of the grid-like container is "afxGWOleControl", shown as follow.
An interesting thing is, the combo box "window" appear in Spy++ only when it is on selected.
Is there any way to edit/read the text field inside the container?

SPI_GETCURSORSHADOW 0x101A - Trouble getting the boolean

Here's the PowerShell code at the bottom that I've tried to write but failed to retrieve the cursor shadow state.
I've looked at the help here: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
And it states that:
SPI_GETCURSORSHADOW 0x101A
Determines whether the cursor has a shadow around it. The pvParam parameter must point to a BOOL variable that receives TRUE if the shadow is enabled, FALSE if it is disabled. This effect appears only if the system has a color depth of more than 256 colors.
My code attempt which can be saved as a .ps1 file and tested in PS ISE
$CSharpSig = #'
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(
int uiAction, uint uiParam, uint pvParam, uint fWinIni);
'#
$SPI_GETCURSORSHADOW = 0x101A
$CursorRefresh = Add-Type -MemberDefinition $CSharpSig -Name WinAPICall -Namespace SystemParamInfo -PassThru
# SPI_GETCURSORSHADOW - pvParam 0 or 1 (3rd argument)
$CursorRefresh::SystemParametersInfo($SPI_GETCURSORSHADOW, 0, $BOOLTOGGLE, 0)
write-output $BOOLTOGGLE
This returns False as the state all the time even though that is not the case.
I'm at a loss how to get this even after reading a similar thread which is not directly associated with PowerShell:
Messed with SystemParametersInfo and Booleans pvParam parameter
Edit, new issue:
My full intention for the code was to try toggle the setting for the shadow of the cursor so here's my latest attempt to do so. I've put the comments relating to the problem inside the code.
This code works for my initial problem but not when I add back in the commented code at the bottom.
# More info here: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
# Get the original setting
$CSharpSigGet = #'
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(
int uiAction, uint uiParam, out bool pvParam, uint fWinIni);
'#
$SPI_GETCURSORSHADOW = 0x101A
$CursorGet = Add-Type -MemberDefinition $CSharpSigGet -Name WinAPICall -Namespace SystemParamInfo -PassThru
[bool] $getBool = $false
$CursorGet::SystemParametersInfo($SPI_GETCURSORSHADOW, 0, [ref] $getBool, 0).value # Stores the boolean.
$cursorShadowBool = ([ref] $getBool).value
write-output $cursorShadowBool
# Toggle the original setting
# This code is now not working as it throws an error when this code below is uncommented..
# Error below:
# TYPE_ALREADY_EXISTS,Microsoft.PowerShell.Commands.AddTypeCommand
# $SPI_SETCURSORSHADOW = 0x101B
# $CSharpSigSet = #'
# [DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
# public static extern bool SystemParametersInfo(
# int uiAction, uint uiParam, uint pvParam, uint fWinIni);
# '#
# $CursorSet = Add-Type -MemberDefinition $CSharpSigSet -Name WinAPICall -Namespace SystemParamInfo -PassThru
# $CursorSet::SystemParametersInfo($SPI_SETCURSORSHADOW, 0, -not ([ref] $getBool).value, 0)
Per the docs:
SPI_GETCURSORSHADOW 0x101A
Determines whether the cursor has a shadow around it. The pvParam
parameter must point to a BOOL variable that receives TRUE if the
shadow is enabled, FALSE if it is disabled. This effect appears only
if the system has a color depth of more than 256 colors.
So your signature is incorrect; the third parameter must point to a BOOL. Assuming you only need to call it for this specific case and we don't need to bother with a generic IntPtr:
$CSharpSig = #'
[DllImport("user32.dll")]
public static extern bool SystemParametersInfo(
int uiAction, uint uiParam, out bool pvParam, uint fWinIni);
'#
[bool] $BOOLTOGGLE = $false
if ($CursorRefresh::SystemParametersInfo($SPI_GETCURSORSHADOW, 0, [ref] $BOOLTOGGLE, 0)) {
write-output $BOOLTOGGLE
}

System.Threading.Timer kills the PowerShell console

When I run this in a PowerShell console:
$callback = [System.Threading.TimerCallback]{
param($state)
}
$timer = [System.Threading.Timer]::new($callback, $null,
[timespan]::Zero,
[timespan]::FromSeconds(1))
Then once $callback gets called (I made sure that this is the root cause, by changing the dueTime constructor's parameter from [timespan]::Zero to longer delays), the whole console process crashes, saying that powershell has stopped working.
What could be the reason? how can I overcome this?
The error is:
There is no Runspace available to run scripts in this thread. You can
provide one in the DefaultRunspace property of the
System.Management.Automation.Runspaces.Runspace type.
And stems from
System.Management.Automation.ScriptBlock.GetContextFromTLS()
The delegate is the issue. This is how I got it working:
$helper = #'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Runspaces;
public class RunspacedDelegateFactory
{
public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace)
{
Action setRunspace = () => Runspace.DefaultRunspace = runspace;
return ConcatActionToDelegate(setRunspace, _delegate);
}
private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments)
{
var invokeMethod = _delegate.GetType().GetMethod("Invoke");
return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments);
}
public static Delegate ConcatActionToDelegate(Action a, Delegate d)
{
var parameters =
d.GetType().GetMethod("Invoke").GetParameters()
.Select(p => Expression.Parameter(p.ParameterType, p.Name))
.ToArray();
Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters));
var lambda = Expression.Lambda(d.GetType(), body, parameters);
var compiled = lambda.Compile();
return compiled;
}
}
'#
add-type -TypeDefinition $helper
$runspacedDelegate = [RunspacedDelegateFactory]::NewRunspacedDelegate($callback, [Runspace]::DefaultRunspace)
$timer = [System.Threading.Timer]::new(
$runspacedDelegate,
$null,
[timespan]::Zero,
[timespan]::FromSeconds(1))
I borrowed NewRunspacedDelegate from
https://www.powershellgallery.com/packages/ContainerTools/0.0.1

Setting screen resolution on Windows 10

I'm trying to set the screen resolution via script (Powershell).
I found the cmdlet Set-DisplayResolution, which should work on Server 2012/R2 and 8/8.1 (where it won't work), and it also didn't work on Windows 10 PS 5.0.
I also had a look at
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Screen]::AllScreens
and
[System.Windows.Forms.Screen]::PrimaryScreen
but it showed weird output:
BitsPerPixel : 32
Bounds : {X=0,Y=0,Width=2000,Height=1333}
DeviceName : \\.\DISPLAY1
Primary : True
WorkingArea : {X=0,Y=0,Width=2000,Height=1293}
I am running a 3000x2000 resolution. Even after I changed my display to 1920x1080 the output stays the same.
After a longer search I found this script, which does the job using win32 API. Is there a more handy way for doing this, as this Set-DisplayResolution just running on Windows 10 Installation?
I also would like to set the UI scaling of Windows 10 (like 150%).
And for sure, i would like to know, why the AllScreens and PrimaryScreen methods of [System.Windows.Forms.Screen] aren't functioning properly.
Refer below script to change display resolution on Windows 10. PowerShell server core does not supported on windows 10. You can use below script
Important : you can provide display resolution at end of the script
currently it is set as Set-ScreenResolution -Width 1920 -Height 1080. You can change these values. Copy this complete script and save as Set-ScreenResolution.ps1 and execute using PowerShell. Make sure you Run powershell as admin
Function Set-ScreenResolution {
<#
.Synopsis
Sets the Screen Resolution of the primary monitor
.Description
Uses Pinvoke and ChangeDisplaySettings Win32API to make the change
.Example
Set-ScreenResolution -Width 1024 -Height 768
#>
param (
[Parameter(Mandatory=$true,
Position = 0)]
[int]
$Width,
[Parameter(Mandatory=$true,
Position = 1)]
[int]
$Height
)
$pinvokeCode = #"
using System;
using System.Runtime.InteropServices;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
class User_32
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
}
public class PrmaryScreenResolution
{
static public string ChangeResolution(int width, int height)
{
DEVMODE1 dm = GetDevMode1();
if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm))
{
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST);
if (iRet == User_32.DISP_CHANGE_FAILED)
{
return "Unable To Process Your Request. Sorry For This Inconvenience.";
}
else
{
iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY);
switch (iRet)
{
case User_32.DISP_CHANGE_SUCCESSFUL:
{
return "Success";
}
case User_32.DISP_CHANGE_RESTART:
{
return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode.";
}
default:
{
return "Failed To Change The Resolution";
}
}
}
}
else
{
return "Failed To Change The Resolution.";
}
}
private static DEVMODE1 GetDevMode1()
{
DEVMODE1 dm = new DEVMODE1();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
"#
Add-Type $pinvokeCode -ErrorAction SilentlyContinue
[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height)
}
Set-ScreenResolution -Width 1920 -Height 1080
Sorry to tell, but it's not possible with powershell:
[System.Windows.Forms.Screen] - the namespace says all you need to understand: This namespace refers to the display area for the application you've coded as an windows form (Usefull, if you have to change the windows-size within your application)
The Cmdlet Set-Displayresolution only works on Windows Server 2016 Core (or any other Core-Server in the future). It is documented on Technet : https://technet.microsoft.com/de-de/library/jj603036(v=wps.630).aspx
The workaround to use Win32-API, that you found earlier, looks like the best solution you could get.
A bit late, but depending on how you need the resolution set you could use AHK (scripting language). This would require a script opening the display settings dialog and sending clicks or keystrokes to it. If you're interested I can give more information
Update:
First, you have to have AutoHotKey installed on your computer (this is straightforward enough to just Google). Next, use a script to send keyboard input to the control panel display settings. It might look something like this:
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#SingleInstance, force
detecthiddenwindows, on
; Use command prompt to open display settings
Run, %comspec% \k,,hide, cmd_pid
WinWait, ahk_pid %cmd_pid%
ControlSend, , control desk.cpl`n, ahk_pid %cmd_pid%
; Wait for display settings to open
WinWaitActive, Settings
sleep, 2000
; Send the appropriate key strokes to the settings menu to change the resolution
Send,{Tab 3}
sleep 100
Send,{Enter}
sleep 100
; Change this as needed to move to the desired display settings
Send,{Down 2}
sleep 100
Send,{Enter}
return