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
Related
Was wondering if there is a way to check what event closed the window, pretty much either clicking the red x in the top corner or if $form.Close() was called?
Each will automatically initiate the $form.Add_Closing({}) if I have it in my script, but I wanted to know which way of closing the window did this.
The FormClosing event argument object's .CloseReason property doesn't allow you to distinguish between the .Close() method having been called on the form and the user closing the form via the title bar / window system menu / pressing Alt+F4 - all these cases equally result in the .CloseReason property reflecting enumeration value UserClosing.
However, you can adapt the technique from Reza Aghaei's helpful C# answer on the subject, by inspecting the call stack for a call to a .Close() method:
using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing
# Create a sample form.
$form = [Form] #{
ClientSize = [Point]::new(400,100)
Text = 'Closing Demo'
}
# Create a button and add it to the form.
$form.Controls.AddRange(#(
($btnClose = [Button] #{
Text = 'Close'
Location = [Point]::new(160, 60)
})
))
# Make the button call $form.Close() when clicked.
$btnClose.add_Click({
$form.Close()
})
# The event handler called when the form is closing.
$form.add_Closing({
# Look for a call to a `.Close()` method on the call stack.
if ([System.Diagnostics.StackTrace]::new().GetFrames().GetMethod().Name -ccontains 'Close') {
Write-Host 'Closed with .Close() method.'
} else {
Write-Host 'Closed via title bar / Alt+F4.'
}
})
$null = $form.ShowDialog() # Show the form modally.
$form.Dispose() # Dispose of the form.
If you run this code and try various methods of closing the form, a message indicating the method used should print (.Close() call vs. title bar / Alt+F4).
Note:
Closing the form via buttons assigned to the form's .CancelButton and .SubmitButton properties that don't have explicit $form.Close() calls still causes .Close() to be called behind the scenes.
The code requires PowerShell v5+, but it can be adapted to earlier versions.
Checking call stack works fine and you can rely on it.
Just for the sake of completeness, specially for cases that you find a C# example and you want to use it in PowerShell in an easy way, I'll share an example showing how you can handle WM_SYSCOMMAND as shown in my linked post, in PowerShell.
using assembly System.Windows.Forms
using namespace System.Windows.Forms
using namespace System.Drawing
# Create the C# derived Form
$assemblies = "System.Windows.Forms", "System.Drawing"
$code = #'
using System;
using System.Windows.Forms;
public class MyForm:Form
{
public bool ClosedByXButtonOrAltF4 { get; private set;}
public const int SC_CLOSE = 0xF060;
public const int WM_SYSCOMMAND = 0x0112;
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SYSCOMMAND && msg.WParam.ToInt32() == SC_CLOSE)
ClosedByXButtonOrAltF4 = true;
base.WndProc(ref msg);
}
protected override void OnShown(EventArgs e)
{
ClosedByXButtonOrAltF4 = false;
}
}
'#
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp
# Create an instance of MyForm.
$form = [MyForm] #{
ClientSize = [Point]::new(400,100)
Text = "Closing Demo"
}
# Create a button and add it to the form.
$form.Controls.AddRange(#(
($btnClose = [Button] #{
Text = "Close"
Location = New-Object System.Drawing.Point 160, 60
})
))
# Make the button call $form.Close() when clicked.
$btnClose.add_Click({
$form.Close()
})
# The event handler called when the form is closing.
$form.add_Closing({
if ($form.ClosedByXButtonOrAltF4) {
Write-Host 'Closed via title bar / Alt+F4.'
} else {
Write-Host 'Closed with .Close() method.'
}
})
$null = $form.ShowDialog()
$form.Dispose()
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.
What I wish is to catch the keypress using JScript .NET, and compile the code using that jsc.exe.
So, is there any equivalent of "addEventListener("keyDown", keyCheck)" from FLASH actionscript. Or GetAsyncKeyState() from C++.
And what library do I have to use?
Please be kind enough to share a small, simple example.
Here's a simple solution if you're writing a console app.
import System;
Console.Write("Press the M key... ");
var key:ConsoleKeyInfo;
while (1) {
while (!Console.KeyAvailable) {
System.Threading.Thread.Sleep(1);
}
key = Console.ReadKey(1);
if (key.Key == ConsoleKey.M) break;
}
Console.Write("Accepted.");
Read more about ConsoleKeyInfo.
If you need GetAsyncKeyState(), it is possible to access the method in JScript.NET. A couple days ago I came across a JScript.NET function that exposes Win32 API methods via P/Invoke. Here it is, slightly modified for simpler syntax (allowing pass-through of arguments from API function definitions).
import System;
import System.Reflection;
import System.Reflection.Emit;
// Invoke a Win32 P/Invoke call.
// credit: http://cx20.main.jp/blog/hello/2013/03/07/hello-win32-api-jscript-net-world/
function InvokeWin32(dllName:String, returnType:Type, methodName:String, params:Object[]) {
var paramTypes:Type[] = new Type[params.length];
for (var i:int in params) {
paramTypes[i] = params[i].GetType();
}
// Begin to build the dynamic assembly
var domain = AppDomain.CurrentDomain;
var name = new System.Reflection.AssemblyName('PInvokeAssembly');
var assembly = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule('PInvokeModule');
var type = module.DefineType('PInvokeType',TypeAttributes.Public
+ TypeAttributes.BeforeFieldInit);
// Define the actual P/Invoke method
var method = type.DefineMethod(methodName, MethodAttributes.Public
+ MethodAttributes.HideBySig + MethodAttributes.Static +
MethodAttributes.PinvokeImpl, returnType, paramTypes);
// Apply the P/Invoke constructor
var ctor = System.Runtime.InteropServices.DllImportAttribute.GetConstructor(
[System.String]
);
var attr = new System.Reflection.Emit.CustomAttributeBuilder(ctor, [dllName]);
method.SetCustomAttribute(attr);
// Create the temporary type, and invoke the method.
var realType = type.CreateType();
return realType.InvokeMember(methodName, BindingFlags.Public + BindingFlags.Static
+ BindingFlags.InvokeMethod, null, null, params);
}
With this function, you can expose Win32 DLL methods with the following syntax. (See? Told you it was simpler.)
// ShowWindowAsync(hWnd:IntPtr, nCmdShow:int);
function ShowWindowAsync(... args:Object[]):boolean {
return InvokeWin32("user32.dll", System.Boolean, "ShowWindowAsync", args);
}
// GetWindowLong(hWnd:IntPtr, nIndex:int);
function GetWindowLong(... args:Object[]):int {
return InvokeWin32("user32.dll", System.Int32, "GetWindowLong", args);
}
// FindWindowEx(parentHandle:IntPtr, childAfter:IntPtr,
// lclassName:IntPtr, windowTitle:String);
function FindWindowEx(... args:Object[]):IntPtr {
return InvokeWin32("user32.dll", System.IntPtr, "FindWindowEx", args);
}
And I've never used GetAsyncKeyState(); but since it's a user32.dll method, I'm guessing it'll work the same way. (Edit: It does.)
// GetAsyncKeyState(vKey:int);
function GetAsyncKeyState(... args:Object[]):short {
return InvokeWin32("user32.dll", System.Int16, "GetAsyncKeyState", args);
}
Then for a trivial example:
import System; // for Console methods
import System.Windows.Forms; // for Keys object constants
Console.Write("Press the M key... ");
// while the M key is not being pressed, sleep
while (!GetAsyncKeyState(Keys.M)) {
System.Threading.Thread.Sleep(1);
}
// flush input buffer
while (Console.KeyAvailable) Console.ReadKey(1);
Console.WriteLine("Accepted.");
Our Zend_Log is initialized by only adding the following lines to application.ini
resources.log.stream.writerName = "Stream"
resources.log.stream.writerParams.mode = "a"
So Zend_Application_Resource_Log will create the instance for us.
We are already able to access this instance in controllers via the following:
public function getLog()
{
$bootstrap = $this->getInvokeArg('bootstrap');
//if (is_null($bootstrap)) return false;
if (!$bootstrap->hasPluginResource('Log')) {
return false;
}
$log = $bootstrap->getResource('Log');
return $log;
}
So far, so good.
Now we want to use the same log instance in model classes, where we can not access the bootstrap.
Our first idea was to register the very same Log instance in Zend_Registry to be able to use Zend_Registry::get('Zend_Log') everywhere we want:
in our Bootstrap class:
protected function _initLog() {
if (!$this->hasPluginResource('Log')) {
throw new Zend_Exception('Log not enabled');
}
$log = $this->getResource('Log');
assert( $log != null);
Zend_Registry::set('Zend_Log', $log);
}
Unfortunately this assertion fails ==> $log IS NULL --- but why??
It is clear that we could just initialize the Zend_Log manually during bootstrapping without using the automatism of Zend_Application_Resource_Log, so this kind of answers will not be accepted.
This is the final solution. We basically shall not call the function _initLog()
Big thanks to ArneRie!
// Do not call this function _initLog() !
protected function _initRegisterLogger() {
$this->bootstrap('Log');
if (!$this->hasPluginResource('Log')) {
throw new Zend_Exception('Log not enabled in config.ini');
}
$logger = $this->getResource('Log');
assert($logger != null);
Zend_Registry::set('Zend_Log', $logger);
}
Possible it is not bootstraped at this time, try:
try {
$this->bootstrap('log'); // bootstrap log
$logger = $this->getResource('log');
} catch (Zend_Application_Bootstrap_Exception $e) {
$logger = new Zend_Log();
$writer = new Zend_Log_Writer_Null();
$logger->addWriter($writer);
}
$registry = Zend_Registry::set('logger', $logger);
Does anyone know how best to determine the specific underlying cause of this exception?
Consider a WCF service that is supposed to use Powershell 2.0 remoting to execute MSBuild on remote machines. In both cases the scripting environments are being called in-process (via C# for Powershell and via Powershell for MSBuild), rather than 'shelling-out' - this was a specific design decision to avoid command-line hell as well as to enable passing actual objects into the Powershell script.
An abridged version of the Powershell script that calls MSBuild is shown below:
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Build.Engine")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = "C:\Windows\Microsoft.NET\Framework\v3.5"
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, "3.5")
$project.Load("deploy.targets")
$project.InitialTargets = "DoStuff"
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = "verbosity=diagnostic"
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
When running the above script from a PS command prompt it all works fine. However, as soon as the script is executed from C# it fails with the above exception.
The C# code being used to call Powershell is shown below (remoting functionality removed for simplicity's sake):
// Build the DTO object that will be passed to Powershell
dto = SetupDTO()
RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create();
using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfig))
{
runspace.Open();
IList errors;
using (var scriptInvoker = new RunspaceInvoke(runspace))
{
// The Powershell script lives in a file that gets compiled as an embedded resource
TextReader tr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("MyScriptResource"));
string script = tr.ReadToEnd();
// Load the script into the Runspace
scriptInvoker.Invoke(script);
// Call the function defined in the script, passing the DTO as an input object
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild", dto, out errors);
}
}
NOTE: The overload of the Invoke() method allows you to pass in an IEnumerable object and it takes care of instantiating an enumerator to in the Powershell variable '$input' - this then gets passed into the script via the pipeline. Here are some supporting links:
http://msdn.microsoft.com/en-us/library/ms569104(VS.85).aspx
http://knicksmith.blogspot.com/2007/03/managing-exchange-2007-recipients-with.html (jump to the 'Passing an Input Object to the Runspace' section)
Assuming that the issue was related to MSBuild outputting something that the Powershell runspace can't cope with, I have also tried the following variations to the second .Invoke() call:
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-String", dto, out errors);
var psResults = scriptInvoker.Invoke("$input | Run-MSBuild | Out-Null", dto, out errors);
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-String");
var psResults = scriptInvoker.Invoke("Run-MSBuild | Out-Null");
var psResults = scriptInvoker.Invoke("Run-MSBuild");
Note how the underlying issue still occurs irrespective of whether an input object is used.
I've also looked at using a custom PSHost (based on this sample: http://blogs.msdn.com/daiken/archive/2007/06/22/hosting-windows-powershell-sample-code.aspx), but during debugging I was unable to see any 'interesting' calls to it being made.
Do the great and the good of Stackoverflow have any insight that might save my sanity?
I can get the following code to work but I get a warning that MSBUILD engine wants to be run on a STA thread. Unfortunately the thread created by the PowerShell engine to execute the script is MTA. That said, my little sample works:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Collections;
namespace RunspaceInvokeExp
{
class Program
{
static void Main()
{
var script = #"
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5""
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"")
$project.Load(""deploy.targets"")
$project.InitialTargets = ""DoStuff""
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = ""verbosity=diagnostic""
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
";
using (var invoker = new RunspaceInvoke())
{
invoker.Invoke(script);
IList errors;
Collection<PSObject> results = invoker.Invoke(#"$input | Run-MSBuild", new[] {0}, out errors);
Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);
}
}
}
}
Which line of your C# code fails? Also, can you post some of the specifics from the exception. You can work around the MTA thread issue by doing something like this:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace RunspaceInvokeExp
{
class Program
{
[STAThread]
static void Main()
{
var script = #"
function Run-MSBuild
{
[System.Reflection.Assembly]::LoadWithPartialName(""Microsoft.Build.Engine"")
$engine = New-Object Microsoft.Build.BuildEngine.Engine
$engine.BinPath = ""C:\Windows\Microsoft.NET\Framework\v3.5""
$project = New-Object Microsoft.Build.BuildEngine.Project($engine, ""3.5"")
$project.Load(""deploy.targets"")
$project.InitialTargets = ""DoStuff""
# Process the input object
while ($input.MoveNext())
{
# Set MSBuild Properties & Item
}
# Optionally setup some loggers (have also tried it without any loggers)
$consoleLogger = New-Object Microsoft.Build.BuildEngine.ConsoleLogger
$engine.RegisterLogger($consoleLogger)
$fileLogger = New-Object Microsoft.Build.BuildEngine.FileLogger
$fileLogger.Parameters = ""verbosity=diagnostic""
$engine.RegisterLogger($fileLogger)
# Run the build - this is the line that throws a CmdletInvocationException
$result = $project.Build()
$engine.Shutdown()
}
Run-MSBuild
";
Runspace runspace = RunspaceFactory.CreateRunspace();
Runspace.DefaultRunspace = runspace;
runspace.Open();
EngineIntrinsics engine = runspace.SessionStateProxy.
GetVariable("ExecutionContext") as EngineIntrinsics;
ScriptBlock scriptblock =
engine.InvokeCommand.NewScriptBlock(script);
Collection<PSObject> results = scriptblock.Invoke(new[] { 0 });
Array.ForEach<PSObject>(results.ToArray(), Console.WriteLine);
runspace.Close(); // Really should be in a finally block
}
}
}