I need to show a PDF file in a browser, by pressing a button in my C# app.
So the browser is eternal to my app.
On Win 10 I do it like this:
....
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = FindBrowser(),
Arguments = $"\"file:///{PDF_File}\""
}
process.Start();
....
private static string FindBrowser()
{
GroupCollection groups;
try
{
// finding the default browser in the registry is a two-step process:
using var key = Registry.CurrentUser.OpenSubKey(
#"SOFTWARE\Microsoft\Windows\Shell\Associations\URLAssociations\http\UserChoice");
var s = (string) key?.GetValue("ProgId");
using var command = Registry.ClassesRoot.OpenSubKey($"{s}\\shell\\open\\command");
var browserCommand = (string) command?.GetValue(null);
var regexpr = new Regex("^\"([^\"]*)\"");
groups = regexpr.Match(browserCommand).Groups;
if (!File.Exists(groups[1].Value))
{
throw new FileNotFoundException("NoBrowser");
}
}
but my customer says this does not work on Win 11 (I have no Win 11)
Is there a foolproof way to program this?
I use VS 2019 myself to develop and debug.
My solution works OK there.
When I try to update Windows features; When I update UseShellExecute to "true"; "The Process object must have the UseShellExecute property set to false in order to redirect IO streams." I get an error. When I set it to False; Unable to update. How can I do it ? Do you have any other suggestions?
static void InstallIISSetupFeature()
{
var featureNames = new List<string>() {
"IIS-WebServerRole",
"IIS-WebServer",
"IIS-CommonHttpFeatures",
"IIS-HttpErrors",
"IIS-HttpRedirect",
"IIS-ApplicationDevelopment",
"IIS-Security",
"IIS-RequestFiltering",
"IIS-NetFxExtensibility",
"IIS-NetFxExtensibility45",
"IIS-HealthAndDiagnostics",
"IIS-HttpLogging",
"IIS-LoggingLibraries",
"IIS-RequestMonitor",
"IIS-HttpTracing",
"IIS-URLAuthorization",
"IIS-IPSecurity",
"IIS-Performance",
"IIS-HttpCompressionDynamic",
"IIS-WebServerManagementTools",
"IIS-ManagementScriptingTools",
"IIS-IIS6ManagementCompatibility",
"IIS-Metabase",
"IIS-HostableWebCore","IIS-StaticContent",
"IIS-DefaultDocument",
"IIS-DirectoryBrowsing",
"IIS-WebDAV",
"IIS-WebSockets",
"IIS-ApplicationInit",
"IIS-ASPNET",
"IIS-ASPNET45",
"IIS-ASP",
"IIS-CGI",
"IIS-ISAPIExtensions",
"IIS-ISAPIFilter",
"IIS-ServerSideIncludes",
"IIS-CustomLogging",
"IIS-BasicAuthentication",
"IIS-HttpCompressionStatic",
"IIS-ManagementConsole",
"IIS-ManagementService",
"IIS-WMICompatibility",
"IIS-LegacyScripts",
"IIS-LegacySnapIn",
"IIS-FTPServer",
"IIS-FTPSvc",
"IIS-FTPExtensibility",
"IIS-CertProvider",
"IIS-WindowsAuthentication",
"IIS-DigestAuthentication",
"IIS-ClientCertificateMappingAuthentication",
"IIS-IISCertificateMappingAuthentication",
"IIS-ODBCLogging",
"NetFx4-AdvSrvs",
"NetFx4Extended-ASPNET45",
"NetFx3",
"WAS-WindowsActivationService",
"WCF-HTTP-Activation",
"WCF-HTTP-Activation45",
"WCF-MSMQ-Activation45",
"WCF-NonHTTP-Activation",
"WCF-Pipe-Activation45",
"WCF-TCP-Activation45",
"WCF-TCP-PortSharing45",
"WCF-Services45",
};
ManagementObjectSearcher obj = new ManagementObjectSearcher("select * from Win32_OperatingSystem");
foreach (ManagementObject wmi in obj.Get())
{
string Name = wmi.GetPropertyValue("Caption").ToString();
Name = Regex.Replace(Name.ToString(), "[^A-Za-z0-9 ]", "");
if (Name.Contains("Server 2008 R2") || Name.Contains("Windows 7"))
{
featureNames.Add("IIS-ASPNET");
featureNames.Add("IIS-NetFxExtensibility");
featureNames.Add("WCF-HTTP-Activation");
featureNames.Add("WCF-MSMQ-Activation");
featureNames.Add("WCF-Pipe-Activation");
featureNames.Add("WCF-TCP-Activation");
featureNames.Add("WCF-TCP-Activation");
}
string Version = (string)wmi["Version"];
string Architecture = (string)wmi["OSArchitecture"];
}
foreach (var featureName in featureNames)
{
Run(string.Format("dism/online/Enable-Feature:{0}", featureName));
}
}
static void Run(string arguments)
{
try
{
string systemPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32");
var dism = new Process();
dism.StartInfo.WorkingDirectory = systemPath;
dism.StartInfo.Arguments = arguments;
dism.StartInfo.FileName = "dism.exe";
dism.StartInfo.Verb = "runas";
dism.StartInfo.UseShellExecute = true;
dism.StartInfo.RedirectStandardOutput = true;
dism.Start();
var result = dism.StandardOutput.ReadToEnd();
dism.WaitForExit();
}
catch (Exception ex)
{
}
}`
I tried to update the feature with dism.exe and cmd.exe, when it gave an authorization error, I used the Verb property
`
Since the use of .Verb = "RunAs" requires .UseShellExecute = true, and since the latter cannot be combined with RedirectStandardOutput = true, you cannot directly capture the elevated process' output in memory.
It seems that the system itself, by security-minded design, prevents a non-elevated process from directly capturing an elevated process' output.
The workaround is to launch the target executable (dism.exe, in your case) indirectly, via a shell, and then use the latter's redirection feature (>) to capture the target executable's output (invariably) in a file, as shown below.
string systemPath = Path.Combine(Environment.ExpandEnvironmentVariables("%windir%"), "system32");
// Create a temp. file to capture the elevated process' output in.
string tempOutFile = Path.GetTempFileName();
var dism = new Process();
dism.StartInfo.WorkingDirectory = systemPath;
// Use cmd.exe as the executable, and pass it a command line via /c
dism.StartInfo.FileName = "cmd.exe" ;
// Use a ">" redirection to capture the elevated process' output.
// Use "2> ..." to also capture *stderr* output.
// Append "2>&1" to capture *both* stdout and stderr in the file targeted with ">"
dism.StartInfo.Arguments =
String.Format(
"/c {0} {1} > \"{2}\"",
"dism.exe", arguments, tempOutFile
);
dism.StartInfo.Verb = "RunAs";
dism.StartInfo.UseShellExecute = true;
dism.Start();
dism.WaitForExit();
// Read the temp. file in which the output was captured...
var result = File.ReadAllText(tempOutFile);
// ... and delete it.
File.Delete(tempOutFile);
First, you can use WindowsPrincipal::IsInRole() to check if you're running elevated.
See Microsoft Learn for details.
Second, this may be one of those cases where using native PS is easier than the cmdlet approach (admittedly, still not great).
If the script is supposed to run on clients as well as server operating systems: use Get-WmiObject or Get-CimInstance to get a reference to what you're running on. ActiveDirectory also has that information (in operatingSystem attribute).
For servers use Get-WindowsFeature in ServerManager module.
For clients use Get-WindowsOptionalFeature with switch -Online in DISM module which, if you indeed need to support OSes older than 6.3.xxxx, can be copied over from a machine that has it and added to $Env:Path before C:\Windows and C:\Windows\System32.
For either platform just pass the list of features to configure.
If in a (binary) cmdlet you have to call external tools then the advantage of them is mostly gone. It may be possible to access Windows CBS using a managed API to avoid this but even then the script based approach gets more results faster, especially since you can just just put together a quick wrapper around dism.exe .
I'm developing a cross-platform application in C# / NET 5, it will run on both Windows and MacOS. I need to print the "manufacturer model name" of the running system. On Windows, this is more or less what is returned by querying the WMI class Win32_ComputerSystem. For example the "Name" field:
Caption: Computer System Product
Description: Computer System Product
IdentifyingNumber: <hidden>
Name: Inspiron 7370 **<--- I need to print this kind of info!**
UUID: <hidden>
Vendor: Dell Inc.
Version:
The software will run also on MacOS, so I need a common way to retrieve, example, "Apple Mac Mini" or similar string. I assume it won't be possible to get it via WMI.
Is there a cross-platform solution? Thanks
I've "solved" by branching between the different OS and without needing to use System.Management package:
public static string GetSystemModelName()
{
var cmd = new ProcessStartInfo();
cmd.RedirectStandardError = true;
cmd.CreateNoWindow = true;
cmd.UseShellExecute = false;
cmd.RedirectStandardOutput = true;
if (System.OperatingSystem.IsWindows())
{
cmd.FileName = "CMD.exe";
cmd.Arguments = "/C wmic csproduct get name | find /v \"Name\"";
}
else if (System.OperatingSystem.IsMacOS())
{
cmd.FileName = "sh";
cmd.Arguments = "-c \"sysctl -n hw.model\"";
}
else return null;
try
{
var builder = new StringBuilder();
using (Process process = Process.Start(cmd))
{
process.WaitForExit();
builder.Append(process.StandardOutput.ReadToEnd());
}
return builder.ToString().Trim();
}
catch (Exception)
{
return null;
}
}
I have a .NET c# application,
the business flow (short and to the point) is:
Users make a call to my app which authenticates them by windows authentication.
If the user is a "special user" (business logic part, E.g. some account admin), I impersonate to a "Master Account" in the active directory which has read / write permissions to a shared folder.
I then create folders and files with the impersonated user context --> This works.
But when I try to start a process (bcp.exe for those who care), I can't get it to work!
After many failing attempts , getting many error messages such as "access denied",
and trying to use almost all of the Process.ProcessStartInfo() attributes which should assist me to run a process as a different user, I decided to Post this as a question.
I've read many blogs suggesting the only way to do this is to use the win32 dll and call CreateProcessAsUser() method, but it's just to damn complicated, and I couldn't find any working sample of it.
bottom line question:
How can I start a Process (Process.Start) from a c# app while in impersonation context as the impersonated user?
My code:
private void ExecuteCommand(string backupSource, string backupFilename, string formatFilename)
{
// This works --> Here I'm under impersonated user context
// with read write permissions to the shared folder
if (!Directory.Exists(OutputPath))
{
Directory.CreateDirectory(OutputPath);
}
using (Process p = new Process())
{
p.StartInfo = GetProcessStartInfo(backupSource, backupFilename, formatFilename);
//Here I'm currently getting ""Access Denied" exception"
p.Start();
...
}
}
private ProcessStartInfo GetProcessStartInfo(string backupSource, string backupFilename, string formatFilename)
{
var result = new ProcessStartInfo();
result.UseShellExecute = false;
result.RedirectStandardOutput = true;
result.RedirectStandardError = true;
var file = Path.Combine(PathToExecutable, "bcp.exe");
// #"C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\110\Tools\Binn\bcp.exe";
result.FileName = file;
result.WorkingDirectory = Path.GetDirectoryName(file);
result.LoadUserProfile = true;
result.Domain = "IMPERSONATED USER DOMAIN";
result.UserName = "IMPERSONATED USER NAME";
var ssPwd = new SecureString();
string password = "IMPERSONATED USER PASSWORD";
for (int x = 0; x < password.Length; x++)
{
ssPwd.AppendChar(password[x]);
}
result.Password = ssPwd;
var backupFullFilename = GetFullFileName(backupFilename);
StringBuilder arguments = new StringBuilder(backupSource);
var formatFullFilename = GetFullFileName(formatFilename);
FormatArguments(arguments, backupFullFilename, formatFullFilename);
var argumentsString = arguments.ToString();
result.Arguments = argumentsString;
return result;
}
Edit #1:
I was able to resolve the "Access is denied" exception, by adding the impersonating user to the administrators group on the machine which the application that starts the process runs on.
Now, I'm having a different issue, no exception but seems like the process isn't starting, or exiting right on start, I'm getting exit code 1073741502.
I've read I must use the native win32 api CreateProcessAsUser() instead of System.Diagnostics.Process.Start() but I'm not sure if that's true.
Ideas?
Assistance would be appreciated.
I have ASP.NET web pages for which I want to build automated tests (using WatiN & MBUnit). How do I start the ASP.Net Development Server from my code? I do not want to use IIS.
This is what I used that worked:
using System;
using System.Diagnostics;
using System.Web;
...
// settings
string PortNumber = "1162"; // arbitrary unused port #
string LocalHostUrl = string.Format("http://localhost:{0}", PortNumber);
string PhysicalPath = Environment.CurrentDirectory // the path of compiled web app
string VirtualPath = "";
string RootUrl = LocalHostUrl + VirtualPath;
// create a new process to start the ASP.NET Development Server
Process process = new Process();
/// configure the web server
process.StartInfo.FileName = HttpRuntime.ClrInstallDirectory + "WebDev.WebServer.exe";
process.StartInfo.Arguments = string.Format("/port:{0} /path:\"{1}\" /virtual:\"{2}\"", PortNumber, PhysicalPath, VirtualPath);
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
// start the web server
process.Start();
// rest of code...
From what I know, you can fire up the dev server from the command prompt with the following path/syntax:
C:\Windows\Microsoft.NET\Framework\v2.0.50727\Webdev.WebServer.exe /port:[PORT NUMBER] /path: [PATH TO ROOT]
...so I could imagine you could easily use Process.Start() to launch the particulars you need through some code.
Naturally you'll want to adjust that version number to whatever is most recent/desired for you.
Building upon #Ray Vega's useful answer, and #James McLachlan's important update for VS2010, here is my implementation to cover VS2012 and fallback to VS2010 if necessary. I also chose not to select only on Environment.Is64BitOperatingSystem because it went awry on my system. That is, I have a 64-bit system but the web server was in the 32-bit folder. My code therefore looks first for the 64-bit folder and falls back to the 32-bit one if necessary.
public void LaunchWebServer(string appWebDir)
{
var PortNumber = "1162"; // arbitrary unused port #
var LocalHostUrl = string.Format("http://localhost:{0}", PortNumber);
var VirtualPath = "/";
var exePath = FindLatestWebServer();
var process = new Process
{
StartInfo =
{
FileName = exePath,
Arguments = string.Format(
"/port:{0} /nodirlist /path:\"{1}\" /virtual:\"{2}\"",
PortNumber, appWebDir, VirtualPath),
CreateNoWindow = true,
UseShellExecute = false
}
};
process.Start();
}
private string FindLatestWebServer()
{
var exeCandidates = new List<string>
{
BuildCandidatePaths(11, true), // vs2012
BuildCandidatePaths(11, false),
BuildCandidatePaths(10, true), // vs2010
BuildCandidatePaths(10, false)
};
return exeCandidates.Where(f => File.Exists(f)).FirstOrDefault();
}
private string BuildCandidatePaths(int versionNumber, bool isX64)
{
return Path.Combine(
Environment.GetFolderPath(isX64
? Environment.SpecialFolder.CommonProgramFiles
: Environment.SpecialFolder.CommonProgramFilesX86),
string.Format(
#"microsoft shared\DevServer\{0}.0\WebDev.WebServer40.EXE",
versionNumber));
}
I am hoping that an informed reader might be able to supply the appropriate incantation for VS2013, as it apparently uses yet a different scheme...
You can easily use Process Explorer to find complete command line options needed for manually start it.
Start Process Explorer while debugging your website. For VS2012, expand 'devenv.exe' node. Right-click on 'WebDev.WebServer20.exe' and from there you can see Path and Command Line values.