I have a utility programs’s EXE file, when i run this file there is a winform only and there is button when we click on it, it run windows’s notepad. Now I want to hijack this program’s command to run notepad and instead of running notepad I want to run MS Word. I know C# and VB.NET. What I need to do this ?
You can try to add in folder with this program your own program called notepad.exe that should do only one thing: run word.
If you want to do it programatically in C then you should read this page - maybe it helps: Intercepted: Windows Hacking via DLL Redirection
You can use a trick to replace programs with another by making changes to the registry. This will work even if the program you are running uses absolute paths to run notepad. It overrides any instance of the running program with the chosen one no matter where it resides. And you won't have to patch the file. The key you'd be interested in is:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
Add a key with the name of the program and add a Debugger string with the path to the program you want to replace it with. Of course you need to have permissions to make the necessary modifications. This page explains how you can replace Windows Notepad with another program. You can apply the same process here.
Though you'll probably not want to have this permanent change, so you can write up a program to temporarily add/change the key, run your program then change it back. Here's a complete one I just whipped up to temporarily replace Notepad with Word for a demonstration. Seems to work perfectly fine (though as always, use at your own risk). Just make all the necessary changes to fit your situation.
using System.Diagnostics;
using Microsoft.Win32;
namespace ProgramLauncher
{
class Program
{
// change the following constants as needed
const string PROGRAM_NAME = #"notepad.exe";
const string REPLACEMENT_PATH = #"C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE";
const string RUNNING_PATH = #"C:\Windows\notepad.exe";
// root key
const string KEY = #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options";
static void Main(string[] args)
{
using (var rootKey = Registry.LocalMachine.OpenSubKey(KEY, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
var oldPath = default(string);
var needsRestoration = false;
try
{
oldPath = BackupKey(rootKey, PROGRAM_NAME, REPLACEMENT_PATH);
needsRestoration = true;
Process.Start(RUNNING_PATH).WaitForExit();
}
finally
{
if (needsRestoration)
RestoreKey(rootKey, PROGRAM_NAME, oldPath);
}
}
}
static string BackupKey(RegistryKey rootKey, string programName, string newPath)
{
Debug.Assert(rootKey != null);
Debug.Assert(!string.IsNullOrEmpty(programName));
Debug.Assert(!string.IsNullOrEmpty(newPath) && System.IO.File.Exists(newPath));
if (newPath.Contains(" "))
newPath = string.Format("\"{0}\"", newPath);
using (var programKey = rootKey.CreateSubKey(programName, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
var oldDebugger = programKey.GetValue("Debugger") as string;
programKey.SetValue("Debugger", newPath, RegistryValueKind.String);
return oldDebugger;
}
}
static void RestoreKey(RegistryKey rootKey, string programName, string oldPath)
{
Debug.Assert(rootKey != null);
Debug.Assert(!string.IsNullOrEmpty(programName));
if (oldPath != null)
{
using (var programKey = rootKey.OpenSubKey(programName, RegistryKeyPermissionCheck.ReadWriteSubTree))
programKey.SetValue("Debugger", oldPath);
}
else
{
rootKey.DeleteSubKey(programName);
}
}
}
}
Related
I try to get the Full Path of a File. ie. calc
Input: calc
Expected output: C:\WINDOWS\system32\calc.exe
I could find out how to do it with PowerShell:
(Get-Command calc).Source
Or with CommandLine:
where.exe calc
But unfortunately I can not get it done with C#.
The documentation for Get-Command says:
Get-Command * gets all types of commands, including all of the non-PowerShell files in the Path environment variable ($env:Path), which it lists in the Application command type.
So we will need to get the Path environment variable and iterate over the directories it lists, looking for files with extensions that indicate the file is a program, for example "*.com" and "*.exe".
The problem with the Path environment variable is that it can become polluted with non-existent directories, so we will have to check for those.
The case of the filename and extension don't matter, so case-insensitive comparisons need to be made.
static void ShowPath(string progName)
{
var extensions = new List<string> { ".com", ".exe" };
string envPath = Environment.GetEnvironmentVariable("Path");
var dirs = envPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string d in dirs.Where(f => Directory.Exists(f)))
{
foreach (var f in (Directory.EnumerateFiles(d).
Where(thisFile => extensions.Any(h => Path.GetExtension(thisFile).Equals(h, StringComparison.InvariantCultureIgnoreCase)))))
{
if (Path.GetFileNameWithoutExtension(f).Equals(progName, StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine(f);
return;
}
}
}
Console.WriteLine("Not found.");
}
static void Main(string[] args)
{
ShowPath("calc");
Console.ReadLine();
}
Output:
C:\WINDOWS\system32\calc.exe
There is always the possibility that the current user does not have permission to list the files from somewhere in the path, so checks should be added for that. Also, you might want to use StringComparison.CurrentCultureIgnoreCase for the comparison.
You can get the Pathenvironment variable, split it with ; as delimiter and loop over that result. Then, check if the file path + #"\" + name + ".exe" exists.
var findMe = "calc";
var pathes = Environment.GetEnvironmentVariable("Path").Split(';');
foreach (var path in pathes)
{
var testMe = $#"{path}\{findMe}.exe";
if (File.Exists(testMe))
{
Console.WriteLine(testMe);
}
}
This outputs :
C:\WINDOWS\system32\calc.exe
I do not know about any way of doing that exact thing from C# either. However the paths are usually well known and can be retreived via the SpecialFolders Enumeration:
using System;
using System.Diagnostics;
using System.IO;
namespace RunAsAdmin
{
class Program
{
static void Main(string[] args)
{
/*Note: Running a batch file (.bat) or similar script file as admin
Requires starting the interpreter as admin and handing it the file as Parameter
See documentation of Interpreting Programm for details */
//Just getting the Absolute Path for Notepad
string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string FullPath = Path.Combine(windir, #"system32\notepad.exe");
//The real work part
//This is the programm to run
ProcessStartInfo startInfo = new ProcessStartInfo(FullPath);
//This tells it should run Elevated
startInfo.Verb = "runas";
//And that gives the order
//From here on it should be 100% identical to the Run Dialog (Windows+R), except for the part with the Elevation
System.Diagnostics.Process.Start(startInfo);
}
}
}
I did not just use System (37) back then, as I wrote it when x32/x86 Systems were still a thing. You would need to check how it resolves nowadays.
Note that most of those paths are duplicated in the PATH System Variable, so you could look it up: https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/
Path Variables in turn go back to the old DOS days. Basically if you gave the Commandline a command/filename it would try the build-in commands, then Executables in the current working Directory (.bat, .com, .exe), and then go look over the path directories to again look for executeables. And only if all that failed, would it complain.
I finally tried to combine all three answers and came up with this:
I post it here in case someone has the same problem.
public static string[] GetPathOf(string cmd)
{
var list = new List<string>();
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.Machine).Split(';'));
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.Process).Split(';'));
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.User).Split(';'));
list = list.Distinct().Where(e=>Directory.Exists(e)).SelectMany(e=> new DirectoryInfo(e).GetFiles()).Where(e=>Regex.IsMatch(e.Name,"(?i)^"+cmd+"\\.(?:exe|cmd|com)")).Select(e=>e.FullName).ToList();
return list.ToArray();
}
private static string getPath(object id11)
{
string wmiQuery = string.Format("select CommandLine from Win32_Process where ProcessId={0}", id11);
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery))
{
using (ManagementObjectCollection retObjectCollection = searcher.Get())
{
foreach (ManagementObject retObject in retObjectCollection)
{
if (retObject["CommandLine"] != null)
{
string s= (string.Format("[{0}]", retObject["CommandLine"]));
string k = s.Substring(s.IndexOf("EXE")+4);
k = k.Remove(k.IndexOf("]"));
return k;
}
return null;
}
return null;
}
}
I use this code to get the notepad full path. This code is work fine when notepad file is open using double click. But When i open file inside notepad like (File->Open)... than this code not work to get a full path. Is there any way to find the path of file open like this. And one more thing i need file path not notepad Executable Path. Or suggest me some other solutions.
Your code looks at the command line arguments sent to a process. As you have rightly found, when you double click a file (.txt or .doc), it may be send as command line argument to the file. Your solution rightly finds the file in those cases.
But, when you open the file from the application, there is no command line argument.
One way is to use a tool like Handle to get the list of process which has your file open.
Sample screen shot:
You can use the Process class to run it and parse the output.
Certain processes (like notepad) will NOT lock the file. So, this tool will not give you the names of those files.
I've created a WPF Form application that can be called via the Windows Context menu. The application works great when I debug it. When I deploy it and right click on a file, if it has any spaces in the path, it only reads the path up to the space, but opens up a window for each space in the path. This is very annoying and I'm not sure how to fix this.
I've tried using Environment.GetCommandLineArgs() in my method as well as just using MainWindow(string filepath); both methods create the same problem.
This is a ClickOnce application so not sure if this would have anything to do with it, but I do capture the directory location of the executing assembly when it gets installed for the first time and have it update the registry in the HKEY_CLASSES_ROOT*\shell[APP}\command key. The default value is an expanded string with the location of the ClickOnce App executable and I have added "%1" so the filepath can be passed into it as an argument.
void checkRegistry()
{
RegistryKey baseKey=Registry.ClassesRoot.CreateSubKey("*\\shell", RegistryKeyPermissionCheck.ReadWriteSubTree);
//add the key
RegistryKey menuKey=baseKey.CreateSubKey("GetCRC");
menuKey.SetValue("", "Get CRC");
//add the command key
RegistryKey commandKey = menuKey.CreateSubKey("command");
string appfilePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
commandKey.SetValue("",String.Format("{0}\\GetCRC2.exe %1",appfilePath), RegistryValueKind.ExpandString);
commandKey.Close();
menuKey.Close();
baseKey.Close();
}
public MainWindow(string filePath)
{
InitializeComponent();
//check registry and add menu option
checkRegistry();
var args = Environment.GetCommandLineArgs();
if (args.Count() > 1)
{
this.filepath = args[1];
}
else
{
//run the file signatures
this.filepath = filePath;
}
textboxFilePath.Text = filepath;
calcSignatures();
}
public MainWindow()
{
InitializeComponent();
//check registry and add menu option if it doesn't exist
checkRegistry();
}
Try putting quotes around application name and file name:
string.Format("\"{0}\\GetCRC2.exe\" \"%1\"",appfilePath)
if the file is named "long hair"
args[1] will be "long" and args[2] will be "hair"
You should either input the filename in quotes or do something to deal with all the user input from the command line, like I dunno...
for args.length keep adding args and a space to the filename variable.
Sorry I'm not writing actual code, I barely know any c#
I am using visual studio 2010 and I am having a .DWG file which I want to open in autocad. Till now I have used this.
Process p = new Process();
ProcessStartInfo s = new ProcessStartInfo("D:/Test File/" + fileName);
p.StartInfo = s;
p.Start();
But what I want is to close the file inside the Autocad but not the autocad itself. (Means atocad.exe should be kept running).
Till now I hve used this but its closing the acad.exe not the file.
foreach (Process Proc in Process.GetProcesses())
{
if (Proc.ProcessName.Equals("acad"))
{
Proc.CloseMainWindow();
Proc.Kill();
}
}
Take the Autocad .NET libraries from Autodesk Sites (http://usa.autodesk.com/adsk/servlet/index?id=773204&siteID=123112)
Then you will be able to use Application and Document classes.
They will give you full control over opening and closing documents within the application.
You can find many articles on that, and can ask further questions.
AutoCAD does have an api. there are 4 assemblys. Two for in-process and two for COM.
inprocess :
acdbmgd.dll
acmgd.dll
COMInterop :
Autodesk.Autocad.Interop.dll
Autodesk.Autocad.Interop.Common.dll
this is a method that will open a new instance of AutoCAD or it will connect to an existing running instance of AutoCAD.
you will need to load these .dlls into your project references.
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
namespace YourNameSpace {
public class YourClass {
AcadApplication AcApp;
private const string progID = "AutoCAD.Application.18.2";// this is AutoCAD 2012 program id
private string profileName = "<<Unnamed Profile>>";
private const string acadPath = #"C:\Program Files\Autodesk\AutoCAD 2012 - English\acad.exe";
public void GetAcApp()
{
try
{
AcApp = (AcadApplication)Marshal.GetActiveObject(progID);
} catch {
try {
var acadProcess = new Process();
acadProcess.StartInfo.Arguments = string.Format("/nologo /p \"{0}\"", profileName);
acadProcess.StartInfo.FileName = (#acadPath);
acadProcess.Start();
while(AcApp == null)
{
try { AcApp = (AcadApplication)Marshal.GetActiveObject(progID); }
catch { }
}
} catch(COMException) {
MessageBox.Show(String.Format("Cannot create object of type \"{0}\"",progID));
}
}
try {
int i = 0;
var appState = AcApp.GetAcadState();
while (!appState.IsQuiescent)
{
if(i == 120)
{
Application.Exit();
}
// Wait .25s
Thread.Sleep(250);
i++;
}
if(AcApp != null){
// set visibility
AcApp.Visible = true;
}
} catch (COMException err) {
if(err.ErrorCode.ToString() == "-2147417846"){
Thread.Sleep(5000);
}
}
}
}
}
closeing it is as simple as
Application.Exit();
and forgive the code. its atrocious, this was one of my first methods when i just started developing...
I doubt you will be able to do this unless AutoCAD has an API that you can hook into and ask it to close the file for you.
Your c# app can only do things to the process (acad.exe) , it doesn't have access to the internal operations of that process.
Also, you shouldn't use Kill unless the process has become unresponsive and certainly not immediately after CloseMainWindow.
CloseMainWindow is the polite way to ask an application to close itself. Kill is like pulling the power lead from the socket. You aren't giving it the chance to clean up after itself and exit cleanly.
There is one other possibility - this will only work if your C# code is running on the same machine as the AutoCAD process and it is not really recommended, but, if you are really stuck and are prepared to put up with the hassle of window switching you can send key strokes to an application using the SendKeys command.
MSDN articles here:
http://msdn.microsoft.com/EN-US/library/ms171548(v=VS.110,d=hv.2).aspx
http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send.aspx
Using this you could send the key strokes to simulate the user using the menu commands to close the file.
To perform the closing of file, best way out is to follow the steps at this ObjectARX SDK for c# and change the following code with the below code.
[CommandMethod("CD", CommandFlags.Session)]
static public void CloseDocuments()
{
DocumentCollection docs = Application.DocumentManager;
foreach (Document doc in docs)
{
// First cancel any running command
if (doc.CommandInProgress != "" &&
doc.CommandInProgress != "CD")
{
AcadDocument oDoc =
(AcadDocument)doc.AcadDocument;
oDoc.SendCommand("\x03\x03");
}
if (doc.IsReadOnly)
{
doc.CloseAndDiscard();
}
else
{
// Activate the document, so we can check DBMOD
if (docs.MdiActiveDocument != doc)
{
docs.MdiActiveDocument = doc;
}
int isModified =
System.Convert.ToInt32(
Application.GetSystemVariable("DBMOD")
);
// No need to save if not modified
if (isModified == 0)
{
doc.CloseAndDiscard();
}
else
{
// This may create documents in strange places
doc.CloseAndSave(doc.Name);
}
}
}
I have an application written in C#, and I am seeking to write some information to the hidden ProgramData in order to access the same connection string from both the application's front end and back end.
I am accessing the directory using path variables as follows:
private bool ProgramDataWriteFile(string contentToWrite)
{
try
{
string strProgramDataPath = "%PROGRAMDATA%";
string directoryPath = Environment.ExpandEnvironmentVariables(strProgramDataPath) + "\\MyApp\\";
string path = Environment.ExpandEnvironmentVariables(strProgramDataPath)+"\\MyApp\\ConnectionInfo.txt";
if (Directory.Exists(directoryPath))
{
System.IO.StreamWriter file = new System.IO.StreamWriter(path);
file.Write(contentToWrite);
file.Close();
}
else
{
Directory.CreateDirectory(directoryPath);
System.IO.StreamWriter file = new System.IO.StreamWriter(path);
file.Write(contentToWrite);
file.Close();
}
return true;
}
catch (Exception e)
{
}
return false;
}
This seems to work correctly. However, my question is, when I used this path variable: %AllUsersProfile%(%PROGRAMDATA%)
instead, it expanded into an illegal(and redundant) file path : C:\ProgramData(C:\ProgramData)\
However, I thought that the latter path variable was the correct full name. Was I just using it incorrectly? I need to ensure that this connection info will be accessible to all users, will just using %PROGRAMDATA% allow that? I am using Windows 7 in case that is relevant.
From here:
FOLDERID_ProgramData / System.Environment.SpecialFolder.CommonApplicationData
The user would never want to browse here in Explorer, and settings changed here should affect every user on the machine. The default location is %systemdrive%\ProgramData, which is a hidden folder, on an installation of Windows Vista. You'll want to create your directory and set the ACLs you need at install time.
So, just use %PROGRAMDATA%, or better still:
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)