ShouldProcess doing nothing once loaded into PowerShell - c#

Simple case, trying to code a cmdlet that will prompt the user to confirm their action. This means I have to put ShouldProcess in the ProcessRecord method call, but it always returns true no matter what (without prompting). Here's a simple case:
using System;
using System.Management.Automation;
[Cmdlet(VerbsDiagnostic.Test, "ShouldProcess",
SupportsShouldProcess = true)]
public class TestShouldProcessCommand : Cmdlet
{
[Parameter(Position = 0,
ParameterSetName = "Force")]
public SwitchParameter Force;
protected override void ProcessRecord()
{
if (ShouldProcess("ShouldProcess Target"))
{
if (Force || ShouldContinue("ShouldContinue Query",
"ShouldContinue Caption"))
{
WriteObject("Hello");
}
else
{
ThrowTerminatingError(new ErrorRecord(new Exception(
"Error Message"), "Error ID",
ErrorCategory.InvalidData, "Target Object"));
}
}
}
}
When you load up PowerShell and try to run it (after importing the dll using Import-Module) all you get is this:
PS C:\Users\user> Test-ShouldProcess
ShouldContinue Caption
ShouldContinue Query
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"): Y
Hello
This is the expected output for ShouldContinue, but nothing happens for the ShouldProcess call that comes first. If you just have it print out the return value of the ShouldProcess call, it's always true no matter how I use it. Tested in PowerShell 2 and 3, and on a vanilla machine. Using Visual Studio 2010 Pro.

Try executing the cmdlet with the common parameter -WhatIf. That should cause ShouldProcess() to return false. The idea is to show what the cmdlet would do without actually doing it when -WhatIf is used. So you would want a more descriptive message in the ShouldProcess() call to indicate which pipeline object you're working on (assuming you support pipeline binding).

Related

The term 'Get-WBSummary' is not recognized as a name of a cmdlet [duplicate]

I have below command and it returns me null object . When I run the command separately in PowerShell window I get the right result. Below is my PowerShell method which is calling the command and the also the PowerShell command which I have defined. I am basically looking to return a string value. Please let me know what wrong am I doing?
C# method:
public string RunScript( string contentScript, Dictionary<string, EntityProperty> parameters)
{
List<string> parameterList = new List<string>();
foreach( var item in parameters )
{
parameterList.Add( item.Value.ToString() );
}
using( PowerShell ps = PowerShell.Create() )
{
ps.AddScript( contentScript );
// in ContentScript I get "Get-RowAndPartitionKey" on debugging
ps.AddParameters( parameterList );//I get list of strings
IAsyncResult async = ps.BeginInvoke();
StringBuilder stringBuilder = new StringBuilder();
foreach( PSObject result in ps.EndInvoke( async ) )
// here i get result empty in ps.EndInvoke(async)
{
stringBuilder.AppendLine( result.ToString() );
}
return stringBuilder.ToString();
}
}
}
My Powershell GetRowAndPartitionKey cmdlet definition, which the code above is trying to call:
public abstract class GetRowAndPartitionKey : PSCmdlet
{
[Parameter]
public List<string> Properties { get; set; } = new List<string>();
}
[Cmdlet( VerbsCommon.Get, "RowAndPartitionKey" )]
public class GetRowAndPartitionKeyCmd : GetRowAndPartitionKey
{
protected override void ProcessRecord()
{
string rowKey = string.Join( "_", Properties );
string pKey = string.Empty;
WriteObject( new
{
RowKey = rowKey,
PartitionKey = pKey
} );
}
}
}
When using the PowerShell SDK, if you want to pass parameters to a single command with .AddParameter() / .AddParameters() / AddArgument(), use .AddCommand(), not .AddScript()
.AddScript() is for passing arbitrary pieces of PowerShell code that is executed as a script block to which the parameters added with .AddParameters() are passed.
That is, your invocation is equivalent to & { Get-RowAndPartitionKey } <your-parameters>, and as you can see, your Get-RowAndPartitionKey command therefore doesn't receive the parameter values.
See this answer or more information.
Note: As a prerequisite for calling your custom Get-RowAndPartitionKey cmdlet, you may have to explicitly import the module (DLL) that contains it, which you can do:
either: with a separate, synchronous Import-Module call executed beforehand (for simplicity, I'm using .AddArgument() here, with passes an argument positionally, which binds to the -Name parameter (which also accepts paths)):
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>").Invoke();
or: as part of a single (in this case asynchronous) invocation - note the required .AddStatement() call to separate the two commands:
IAsyncResult async =
ps.AddCommand("Import-Module").AddArgument(#"<your-module-path-here>")
.AddStatement()
.AddCommand("GetRowAndPartitionKey").AddParameter("Properties", parameterList)
.BeginInvoke();
"<your-module-path-here>" refers to the full file-system path of the module that contains the Get-RowAndPartitionKey cmdlet; depending on how that module is implemented, it is either a path to the module's directory, its .psd1 module manifest, or to its .dll, if it is a stand-alone assembly.
Alternative import method, using the PowerShell SDK's dedicated .ImportPSModule() method:
This method obviates the need for an in-session Import-Module call, but requires extra setup:
Create a default session state.
Call .ImportPSModule() on it to import the module.
Pass this session state to PowerShell.Create()
var iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { #"<your-module-path-here>" });
var ps = PowerShell.Create(iss);
// Now the PowerShell commands submitted to the `ps` instance
// will see the module's exported commands.
Caveat: A PowerShell instance reflects its initial session state in .Runspace.InitialSessionState, but as a conceptually read-only property; the tricky part is that it is technically still modifiable, so that mistaken attempts to modify it are quietly ignored rather than resulting in exceptions.
To troubleshoot these calls:
Check ps.HadErrors after .Invoke() / .EndInvoke() to see if the PowerShell commands reported any (non-terminating) errors.
Enumerate ps.Streams.Errors to inspect the specific errors that occurred.
See this answer to a follow-up question for self-contained sample code that demonstrates these techniques.

Cmdlet verbose stream

I'm currently writing a C# cmdlet using the PowerShell 5.0 SDK.
I'm trying to pipe the StandardError of a third party executable to the cmdlet output when run from powershell in "real time".
I'm currently using the MedallionShell library to handle running the process. I've tried this with a normal C# win form and used the Command.StandardError.PipeToAsync(Console.OpenStandardOutput()) to get the output to print as the executable generated it to the console in "real time".
I tried to create my own Stream object that calls WriteVerbose but it didn't seem to print anything to the powershell screen (I am passing -Verbose to cmdlet when I run it).
My current flow looks something like this:
Open Powershell ISE
Load my module (C# dll)
Call my cmdlet with parameters
Command.Run
Command.StandardError.PipeToAsync(???)
Command.Wait (During this step, output should be flowing to powershell window)
Check Command.Result.Success.
Can anyone point me in the right direction on this?
You can not just call Cmdlet's Write methods (like WriteVerbose) from arbitrary thread. You need to marshal calls to this methods back to pipeline thread. A way to do that is to implement message loop, which would process messages from others threads, when other threads want to invoke something in pipeline thread.
Add-Type #‘
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
[Cmdlet(VerbsLifecycle.Invoke, "Process")]
public class InvokeProcessCmdlet : Cmdlet {
[Parameter(Position = 1)]
public string FileName { get; set; }
[Parameter(Position = 2)]
public string Arguments { get; set; }
protected override void EndProcessing() {
using(BlockingCollection<Action> messageQueue = new BlockingCollection<Action>()) {
using(Process process = new Process {
StartInfo=new ProcessStartInfo(FileName, Arguments) {
UseShellExecute=false,
RedirectStandardOutput=true,
RedirectStandardError=true
},
EnableRaisingEvents=true
}) {
int numberOfCompleteRequests = 0;
Action complete = () => {
if(Interlocked.Increment(ref numberOfCompleteRequests)==3) {
messageQueue.CompleteAdding();
}
};
process.OutputDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteObject(args.Data));
}
};
process.ErrorDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteVerbose(args.Data));
}
};
process.Exited+=(sender, args) => complete();
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
foreach(Action action in messageQueue.GetConsumingEnumerable()) {
action();
}
}
}
}
}
’# -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
And you can test it with something like this:
Invoke-Process icacls 'C:\* /c' -Verbose
if you derive from PSCmdlet instrad of Cmdlet you will have access to this.Host.UI.WriteVerboseLine which can be called from any thread at your own risk (i think it doesn't prevents in any way that the outputed string will be mixed in the wrong way)
Anyway, in my experience it have always worked well so far, and if the cmdlet is something that only you will consume i think the risk may be acceptable.
Again, it works well if used in a console, i don't know if it behaves the intended way if you later on redirect the verbose stream somewhere else than the console or something that doesn't have a "UI"
For sure #PetSerAl solution is more appropriate if you have some more time to implement it

Powershell calling a constructor from custom DLL "Exception calling ".ctor" with "1" argument(s): "Index 1 is out of range.""

I am hoping someone can help with this, I searched and can't seem to find an answer or even the exact same problem.
I am loading an assembly from file (even though I have v2 Powershell add-type has some strange behaviour in this instance).
[System.Reflection.Assembly]::LoadFrom("$env:userprogile\path\to\my\dll\mydll.dll")
$taskId = 1
$ts = New-Object mydll.myclass -ArgumentList #(,$taskId)
DLL loads fine and the constructor becomes available as expected but when trying to parse arguments into it, infact it takes just one in this case it throws this error
New-Object : Exception calling ".ctor" with "1" argument(s): "Index 1 is out of range."
At line:1 char:7
+ $ts = New-Object mydll.myclass
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The unit test and test app for this works just fine when an integer of 1 is passed to it.
$taskId -is [int] gives true, I tried passing it these ways:
-ArgumentList 1 -ArgumentList (1) -ArgumentList #(1) and I am sure there were some other more ludicrous ones
Funnily enough all give the same error...
Constructor signature:
public sealed class myclass: IDisposable
{
private readonly int _taskId = 0;
private ScheduledTask scheduledTask = null;
public myclass(int taskId)
{
if (taskId == 0)
throw new ApplicationException("Task Id is not valid");
_taskId = taskId;
scheduledTask = TaskJobFactory.CreateScheduledTask(this._taskId);
}
public void RunTask()
{
DataTable dt = null;
String csvString = String.Empty;
try
{
dt = TaskJobFactory.CreateTableTask(scheduledTask.EnumTaskType, scheduledTask.campaignID.Value);
csvString = Csv.DataTableToCsc(dt);
if (csvString.Length > 0)
{
SmtpManager.SendEmailStatic(csvString, scheduledTask.Id, scheduledTask);
TaskHistoryFactory.UpdateTaskHistory(this._taskId, (int)ScheduledTaskStatus.Success, "Success");
}
else
TaskHistoryFactory.UpdateTaskHistory(this._taskId, (int)ScheduledTaskStatus.Unknown, String.Format("No Data for that Taks id {0}", _taskId));
}
catch (Exception ex)
{
TaskHistoryFactory.UpdateTaskHistory(this._taskId, (int)ScheduledTaskStatus.Failure, "Fail");
ErorrLoggingFacede.LogError(ex, "TaskRunner", "RunTask");
throw ex;
}
finally
{
dt = null;
}
}
}
sorry about the formatting...
Also, I tried having an empty constructor and passing the args directly to method, it worked in the test app (with a few errors since it wasn't built for that but actually completed)
After some debugging - it turns out that the Exception calling ".ctor" with "1" argument(s): "Index 1 is out of range." in the constructor was actually coming from a connection string error, or rather the mydll.dll.config was not being read even though it was in the same directory.
It should be enough for app.config or mydll.dll.config to be in the same directory as the DLL - Putting configuration information in a DLL according to these and many others.
Not so in this case, in first instance the DLL was actually using EF which was a bag of problems on its own, so changed to ADO, but still nothing.
The solution was to use
var appConfig = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location);
string dllConfigData = appConfig.AppSettings.Settings["dllConfigData"].Value;
As suggested in this post - C# DLL config file
This worked great once the path was explicitly named i.e. Assembly.GetExecutingAssembly(#"C:\mypath\to\dll\app.config") but posed an annoying problem of deploying to production where paths would have to be changed before deploying.
The final solution for us was to create our own XML configuration file and pass that to the constructor with other parameters. Works well and can be updated separately to the whole DLL - esp. if you are using third party APIs and store tokens/access keys/etc...

How do i read the Title property of a GDK.Window

I'm converting an .NET Windows application for Mono to run on Linux (Ubuntu). One of the features depends on a native library (user32.dll). The Mono guide that talks about conversion of applications (Linux Platform Differences) suggests that one approach would be to modify this code.
I'm trying to use GDK to access the Title of a Gdk.Window that I had access through the property Gdk.Global.ActiveWindow. But I found this error at compile time:
Error CS0154: The property or indexer `Gdk.Window.Title` cannot be used in this context because it lacks the `get` accessor (CS0154) (GetActiveWindow)
If i remove the code that reads de Title property of activeW, everything works fine. There is another way to read this property?
Here my unit of work:
using System;
using Gtk;
using Gdk;
using System.Threading;
namespace GetActiveWindow
{
class GdkApp : Gtk.Window
{
public static void Main ()
{
Application.Init ();
new GdkApp ();
Application.Run ();
}
public GdkApp () : base("Simple App")
{
SetDefaultSize (150, 150);
ShowAll();
while (true) {
var activeW = Gdk.Global.ActiveWindow;
Console.WriteLine("Active Window: {0}",activeW.Title); // Where my compile error happens.
Console.WriteLine("Simple App Window: {0}",this.Title); // This code works perfectily.
Thread.Sleep(1000);
}
}
}
}
I think that with Gdk is imposible. Try it with Wnck library giving to a C compiler this '-DWNCK_I_KNOW_THIS_IS_UNSTABLE' and works but with a warning: Unhandled action type _OB_WM_ACTION_UNDECORATE
Sorry I have used genie instead vala.
//valac *.gs --pkg gtk+-3.0 --pkg libwnck-3.0 -X '-DWNCK_I_KNOW_THIS_IS_UNSTABLE'
init
Gtk.init(ref args)
var ventana= new win()
ventana.inicio()
ventana.printinfo()
Gtk.main()
class win:Gtk.Window
won:weak GLib.List of Wnck.Window
def inicio()
var button= new Gtk.Button()
button.clicked.connect(printinfo)
this.add(button)
this.show_all()
def printinfo()
won= Wnck.Screen.get_default().get_windows()
won.foreach(allwin)
def allwin(w:Wnck.Window)
if w.is_skip_tasklist() or w.is_skip_pager()
pass
else
print w.get_name()

Run PSCmdLets in C# code (Citrix XenDesktop)

I'm new to PowerShell and running PowerShell cmd-lets in C#. Specifically, I'm trying to use Citrix's XenDesktop SDK to write a web app to manage our XenDesktop environment.
Just as a quick test, I made a reference to the Citrix BrokerSnapIn.dll, which looks like it gives me good C# classes. However, when I hit the .Invoke with this error message:
"Cmdlets derived from PSCmdlet cannot be invoked directly."
I've searched and tried a bunch of stuff, but don't know how to call PSCmdlets. I'm kinda left thinking that I have to use strings and a runspace/pipeline, etc, to do this.
Thanks In Advanced,
NB
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Citrix.Broker.Admin.SDK;
namespace CitrixPowerShellSpike
{
class Program
{
static void Main(string[] args)
{
var c = new GetBrokerCatalogCommand {AdminAddress = "xendesktop.domain.com"};
var results = c.Invoke();
Console.WriteLine("all done");
Console.ReadLine();
}
}
}
You need to host the PowerShell engine in order to execute a PSCmdlet e.g. (from the MSDN docs):
// Call the PowerShell.Create() method to create an
// empty pipeline.
PowerShell ps = PowerShell.Create();
// Call the PowerShell.AddCommand(string) method to add
// the Get-Process cmdlet to the pipeline. Do
// not include spaces before or after the cmdlet name
// because that will cause the command to fail.
ps.AddCommand("Get-Process");
Console.WriteLine("Process Id");
Console.WriteLine("----------------------------");
// Call the PowerShell.Invoke() method to run the
// commands of the pipeline.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["ProcessName"].Value,
result.Members["Id"].Value);
}
}

Categories