I'm writing a PowerShell module in C#. At runtime one of the cmdlets is called like this:
Test-Path -Path \\path\to\somewhere
My constructor looks like this:
public TestPath()
{
checkPathExistence();
}
This is working until the user at runtime presses the Tab key in order to autocomplete parameter name:
Test-Path -Pa <TAB>
The Tab key fires the constructor and this causes my checkPathExistence() method to give unwanted results. How can I make my checkPathExistence() wait for the Enter key before checking anything?
My first idea was to check Path being null. But Path isn't mandatory. If the cmdlet is being called without any parameter some standard path from former sessions is being set.
void checkPathExistence()
{
if (!File.Exists(this.Path))
{
Path = Properties.Settings.Default.Path;
}
else
{
Properties.Settings.Default.Path = Path;
Properties.Settings.Default.Save();
Console.WriteLine("The path has changed to: " + Path);
}
}
}
The problem here is that you are calling checkPathExistence during the constructor. Instead, it sounds like you only want to call it while processing the command-line including the path. Leave the constructor empty, and instead handle the command-line during ProcessRecord. See Cmdlet Input Processing.
Related
Normally shell automation InvokeVerb only accepts a string command, based on the documentation here: https://msdn.microsoft.com/en-us/library/ms723189(v=vs.85).aspx
However I noticed a cmd line EXE utility that accepts a number instead
http://www.technosys.net/products/utils/pintotaskbar
This is interesting because in Windows 10 "&Pin to taskbar" is removed from shell automation, despite being visible in the UI. ( https://connect.microsoft.com/PowerShell/feedback/details/1609288/pin-to-taskbar-no-longer-working-in-windows-10 )
However with this EXE passing the "number" 5386 as the command works.
I am interested to know what methods can be used to achieve this in either PowerShell/VBScript/.NET language or Win32 C/C++.
I understand this is almost certainly unsupported and likely to break any time as these numbers have changed between OS releases.
An example using the string version in PowerShell
$filepath = "C:\windows\system32\notepad.exe"
$path = Split-Path $FilePath
$shell= New-Object -com "Shell.Application"
$folder=$shell.Namespace($path)
$item = $folder.Parsename((split-path $FilePath -leaf))
$item.InvokeVerb("&Pin to taskbar")
The underlying shell context menu extension system actually works with numeric IDs natively; string verbs are an optional layer on top. However as you observe, command IDs are internal to a context menu extension and are not guaranteed to stay the same between versions (or even between invocations) - the whole point of verbs is that they allow commands to be invoked programatically irrespective of their ID.
In C/C++ you can invoke a command by its ID by casting the numeric ID as a string pointer (the rationale being that since all valid string pointers are higher than 65535, it's "safe" to pass a number as a string pointer as long as it fits into 16 bits, since the receiving API is able to correctly determine whether it is a "real string" or not).
Note that this is different from printing the number as a string, which is not correct. The MAKEINTRESOURCE macro exists for exactly this purpose.
E.g., a command with ID 1234 would be invoked using MAKEINTRESOURCE(1234) as the verb.
I ran into the same problem these days, in a Windows Script Host based script.
Invoking the verb with the localized string didn't work as it used to.
However you can still get the item's "Verbs" collection and then enumerate them until you get the verb you want to invoke. Then you can invoke it using it's "DoIt" method, without the need to pass the verbs name in.
In a WSH JScript it looks like this:
/* workaround for 'item.InvokeVerb("&Delete")' which no longer works */
var verbs = item.Verbs(); // item == return of folder.ParseName(..)
for (var x = 0; x < verbs.Count; x++)
{
var verb = verbs.Item(x);
if (verb.Name == "&Delete") //replace with "&Pin to taskbar"
{
try
{
verb.DoIt();
WSH.Echo ("Deleted:", item.Name);
}
catch(e)
{
WSH.Echo ("Error Deleting:", item.Name + "\r\n\t", e.description, e.number);
}
break;
}
}
}
Likely it also works somehow in PowerShell
ffr:
https://learn.microsoft.com/en-us/windows/win32/shell/folderitem-verbs
https://learn.microsoft.com/en-us/windows/win32/shell/folderitemverb
I've managed to create my own file extension following this tutorial: http://www.codeproject.com/Articles/17023/System-File-Association
So far, it works perfectly. I've got only one thing that I can't solve.
When I double-click on a file with that extension, my program opens up. Now, I'd want to perform an action in my program. I made my way through some threads here and read that the file path is automatically passed to the startup arguments.
The problem is that no single argument is passed, also Process.GetCurrentProcess().StartInfo.FileName returned an empty string. I think this is consecutively because I don't pass any arguments when double-clicking my file.
This is my code:
var fai = new FileAssociationInfo(".extension");
if (!fai.Exists)
{
try
{
fai.Create("My Extension Program");
var pai = new ProgramAssociationInfo(fai.ProgId);
if (!pai.Exists)
{
pai.Create("My Program File",
new ProgramVerb("Open", Application.ExecutablePath);
pai.DefaultIcon = new ProgramIcon(Application.ExecutablePath);
}
}
}
As you can see I only pass the application's path to open it up. But how can I pass the file path as argument now? I've seen that e.g. the author of the article passes "%1" as argument, I tried that, too, but nothing changed.
Thanks in advance.
ProcessStartInfo.FileName usually gives you the path to your program executable itself, not the file which was clicked in Windows Explorer, so this seems the wrong thing to check in your case.
If you want to get the arguments using the current Process, then Process.GetCurrentProcess().StartInfo.Arguments should give you a string containing all the arguments passed to the program. If there are multiple arguments, you would need to parse these into separate values yourself.
But the standard, simpler way to get the arguments is to make sure the Main() method of your program has signature static void Main(string[] args){}. args is already processed into separate values for you, so it is easier to handle it here, even if you only pass it off to another class or store them in a static variable.
The %1 should ensure the clicked file is passed as the first argument (args[0]) to your program.
Well, I got it. What I had to do was creating a subkey in ClassesRoot: "ProgramName\shell\open\command". Then set a value containing the application's path and attach "%1" to it and you're done.
I am in a need to pass the value from command line to custom action.The custom action should get the value from command line and modify one file(app.config) during installation of EXE.I have the below code in custom action
if (Context.Parameters["ENV"] == "test") //Test environment
{
authEndPoint = "http://192.168.168.4/api/user_authentication/";
}
else if (Context.Parameters["ENV"] == " ") //Production environment
{
authEndPoint = "https://livesiteurl/api/user_authentication/";
}
I want to install the exe file like below
myapplication.exe env=test
I seen lot of sample to pass the value from command line to msi. How to pass the value from command line to CA and modify the app.config file.
There are better ways to do what you're trying to do here than use a custom action. Take a look at this for how use the WiXUtilExtension to modify the file, then create a property and reference it from the command line. If you still need/want to use a bootstrapper you can set that property you created with MsiProperty inside MsiPackage in the bundle.
I have a simple application that opens a TCP connection and communicates via Telnet to another system. The application is to read a file that contains parameters and a simple scripting language to issue commands based on prompts from the connection.
Written in VS2013 using .NET 4
My application works as designed with one little exception.
I am publishing to a location using VS2013 which works well enough but the idea is to read a command line passed to my application that contains the path/file for the script to execute and that doesn't work as expected.
Finding out the hard way, the standard args[] parameters are not passed when it's published this way.
I have searched out multiple solutions that don't work both on here and other sites.
This is the basis (excerpt from page) of my current implementation to read the command line (found here: http://developingfor.net/2010/06/23/processing-command-line-arguments-in-an-offline-clickonce-application/). This seems to be similar to all solutions I've found, each with some variation that doesn't work.
string[] args = null;
if (ApplicationDeployment.IsNetworkDeployed)
{
var inputArgs = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;
if (inputArgs != null && inputArgs.Length > 0)
{
args = inputArgs[0].Split(new char[] { ',' });
}
}
else
{
args = e.Args;
}
This SHOULD return args[] with parameters passed. I believe it would also include the actual command with path to the application. The Split function is because the author wishes to pass arguments separated by commas and not spaces.
My incarnation of this is a bit longer to include some checks to see if we actually get arguments from being compiled as an exe instead. If I compile to EXE and supply a command line, all is fine. Here is my code, not very concise as I've made lots of changes to debug and make this work.
I haven't figured out how to debug in the ide as network deployed with a command line so my debug code is via messagebox.
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (args.Length == 0) //If we don't have args, assume onclick launch
{
if (ApplicationDeployment.IsNetworkDeployed) //are we oneclick launched?
{
var cmdline = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData; //this should hold the command line and arguments????
if (cmdline != null && cmdline.Length > 0) //we have something and it contains at least 1 value
{
//This is all debug code to see what we get since we can't trace in this mode
MessageBox.Show(cmdline.Length.ToString()); //how many objects do we have?
foreach (String s in cmdline)
{
MessageBox.Show(s); //show us the value of each object
}
Application.Run(new frmMain(args)); //launch the form with our arguments
}
else
{
//quit application
MessageBox.Show("No command line.1"); //debug so we know where we failed
Application.Exit();
}
}
else
{
//quit application
MessageBox.Show("No command line.2"); //debug so we know where we failed
Application.Exit();
}
}
else
{
Application.Run(new frmMain(args)); //launch form with args passed with exe command line
}
}
Running the code above like this:
sTelent.application 1234
I have also explored the URL passing method which seems to only apply if launched from a web server, which this application is not.
At first I got NULL for my object:
AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData
After more research I discovered that in my project properties under the Publish section there is an option button and under Manifests I can choose "Allow URL Parameters to be passed to application"
I checked this box and while I get different behavior, I don't get the desired behavior.
With that option checked I now get 2 messages boxes: The first showing the number of objects in cmdline and that number is 1 and the second showing the value of that one object which contains only the path/command to my application. No other objects and definitely not my arguments.
Am I totally off base? How do I get my command line arguments from an offline clickonce published application?
It seems that you must put the argument on the .appref-ms and not on not the .application or .exe for this to work correctly for clickonce based applications.
I created a short cut on my desktop by copying the installed application link found under All Programs. That should create an icon on your desktop with the same name as your application.
Then, open a command prompt, type in “%userprofile%\Desktop\My App Name.appref-ms” word for word (of course replace "my app name" with your application name). It should then pass the arguments. You can also put the command within a .bat file. I'm sure that you can also reference the link directly, it typically is located under c:\users[user profile]\appdata\roaming\Microsoft\windows\start menu\programs[app name]
I'm showing a FolderBrowser to the user in my application and then promotes him with a ShowDialog() which has m_dialog.Style = FolderBrowserStyles.ShowTextBox;
Thus, allowing the user to manually enter path for the folder he wants to choose.
The problem is that when the user types a path for a folder which doesn't exists and clicks OK, the dialog returns with some default DirectoryPath value.
What I want is the selected folder to be created (if it doesn't exists, and by promoting the user first) and then have the (now valid) path inside the DirectoryPath property.
Any way to do it?
The FolderNameEditor.FolderBrowser class makes use of the SHBrowseForFolder shell function. The default functionality based on the user entering an invalid path is to return the default selected item (which in this case is the Desktop folder).
The SHBrowseForFolder shell function expects an argument of type BROWSEINFO (structure).
This structure allows for the definition of a callback function (a pointer to an application-defined function that the dialog box calls when an event occurs) and it is in this callback that the possibility lies of achieving what you require.
This callback function is set to null when FolderBrowser invokes this shell function, so there is no possible way of achieving what you require using the FolderNameEditor class.
However there is a library on codeproject you can make use of which uses the SHBrowseForFolder and wraps the event callback, providing access to the invalid folder entry through an event (OnValidateFailed). See: C# does Shell, Part 1
Within this event (after some validation (as the user can input anything)) you could use the path entered to create the directory.
Here is an example:
using ShellLib;
...
public class OpenFolderDialog
{
ShellBrowseForFolderDialog folderDialog;
string selectedPath;
public OpenFolderDialog()
{
folderDialog = new ShellBrowseForFolderDialog();
folderDialog.OnValidateFailed += new ShellBrowseForFolderDialog.ValidateFailedHandler(dialog_OnValidateFailed);
}
int dialog_OnValidateFailed(ShellBrowseForFolderDialog sender, ShellBrowseForFolderDialog.ValidateFailedEventArgs args)
{
selectedPath = args.invalidSel;
//Use selectedPath here to create the directory.
return 0;
}
public string GetFolder()
{
selectedPath = string.Empty;
folderDialog.ShowDialog();
return selectedPath == string.Empty ? folderDialog.FullName : selectedPath;
}
}
Hope this helps.