How to get cross-platform system model name with .NET 5? - c#

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;
}
}

Related

How can I programmatically turn off or on 'Windows Features'

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 .

How to uninstall a UWP application programmatically without admin access c#

I have sideloaded a UWP application onto my clients machine.
I would now like to uninstall the program, but without admin access.
I have found Remove-AppxPackage but this uses powershell and so would need an executionpolicy set which would require admin access
For my WPF applications I would just delete the directory containing the application but with a UWP app I'm not even sure what to delete.
Essentially I would like to programatically click the uninstall button on from the Add and remove programs
I did look at this link How to uninstall application programmatically with the code:
public static string GetUninstallCommandFor(string productDisplayName)
{
RegistryKey localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,RegistryView.Registry64);
string productsRoot = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products";
RegistryKey products = localMachine.OpenSubKey(productsRoot);
string[] productFolders = products.GetSubKeyNames();
foreach (string p in productFolders)
{
RegistryKey installProperties = products.OpenSubKey(p + #"\InstallProperties");
if (installProperties != null)
{
string displayName = (string)installProperties.GetValue("DisplayName");
Debug.WriteLine(displayName);
if ((displayName != null) && (displayName.Contains(productDisplayName)))
{
string uninstallCommand = (string)installProperties.GetValue("UninstallString");
return uninstallCommand;
}
}
}
return "";
}
But this didn't find my application - eventhough it is in the "Apps and features" settings page
Ok my solution as advised by Nico Zhu was to use powershell. I created a method like so:
private static void LaunchProcess(string uri, string args)
{
var psi = new ProcessStartInfo();
psi.UseShellExecute = true;
psi.CreateNoWindow = false;
psi.Arguments = args;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.FileName = uri;
var proc = Process.Start(psi);
proc.WaitForExit();
var exitcode = proc.ExitCode;
}
and used it like so:
LaunchProcess("powershell.exe", "get-appxpackage *AppPackageNameThatOnlyMatchesYourAppPackage* | remove-appxpackage");
This process surprisingly didn't require admin rights.
I must say though from a microsoft developer point of view UX. For managing distribution of my UWP apps, this is another thumbs down for UWP vs WPF

Scan uploaded files C# ASP.net

I'm trying to do a virus scan on uploaded files.
I have no control over the installed virus scanner, the product hosted by multiple parties with different scanners.
I tried the following library but it always returns VirusNotFound on the eicar file.
https://antivirusscanner.codeplex.com/
Do you know any other solutions?
ClamAV has pretty bad detection scores.
VirusTotal is not on premises.
I decided to create CLI wrappers for multiple scanners, nuget packages can be found here: https://www.nuget.org/packages?q=avscan
And its documentation and source code available at https://github.com/yolofy/AvScan
I used this library for .net (It uses the VirusTotal public api):
https://github.com/Genbox/VirusTotal.NET
A little example from github :
static void Main(string[] args)
{
VirusTotal virusTotal = new VirusTotal("INSERT API KEY HERE");
//Use HTTPS instead of HTTP
virusTotal.UseTLS = true;
FileInfo fileInfo = new FileInfo("testfile.txt");
//Create a new file
File.WriteAllText(fileInfo.FullName, "This is a test file!");
//Check if the file has been scanned before.
Report fileReport = virusTotal.GetFileReport(fileInfo).First();
bool hasFileBeenScannedBefore = fileReport.ResponseCode == 1;
if (hasFileBeenScannedBefore)
{
Console.WriteLine(fileReport.ScanId);
}
else
{
ScanResult fileResults = virusTotal.ScanFile(fileInfo);
Console.WriteLine(fileResults.VerboseMsg);
}
}
A full example can be found here :
https://github.com/Genbox/VirusTotal.NET/blob/master/VirusTotal.NET%20Client/Program.cs
Clam AV is pretty good.
https://www.clamav.net/downloads
C# Api here:
https://github.com/michaelhans/Clamson/
I just tried various ways, But some didn't work.
Then I decided to use ESET NOD32 command line tools .
It works fine for me:
public bool Scan(string filename)
{
var result = false;
try
{
Process process = new Process();
var processStartInfo = new ProcessStartInfo(#"C:/Program Files/ESET/ESET Security/ecls.exe")
{
Arguments = $" \"{filename}\"",
CreateNoWindow = true,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
};
process.StartInfo = processStartInfo;
process.Start();
process.WaitForExit();
if (process.ExitCode == 0) //if it doesn't exist virus ,it returns 0 ,if not ,it returns 1
{
result = true;
}
}
catch (Exception)
{ //nothing;
}
return result;
}

Get SSID of the wireless network I am connected to with C# .Net on Windows Vista

I'd like to know if there is any .Net class that allows me to know the SSID of the wireless network I'm connected to.
So far I only found the library linked below. Is the best I can get or should I use something else?
Managed WiFi (http://www.codeplex.com/managedwifi)
The method that exploits WMI works for Windows XP but is it not working anymore with Windows Vista.
I resolved using the library. It resulted to be quite easy to work with the classes provided:
First I had to create a WlanClient object
wlan = new WlanClient();
And then I can get the list of the SSIDs the PC is connected to with this code:
Collection<String> connectedSsids = new Collection<string>();
foreach (WlanClient.WlanInterface wlanInterface in wlan.Interfaces)
{
Wlan.Dot11Ssid ssid = wlanInterface.CurrentConnection.wlanAssociationAttributes.dot11Ssid;
connectedSsids.Add(new String(Encoding.ASCII.GetChars(ssid.SSID,0, (int)ssid.SSIDLength)));
}
We were using the managed wifi library, but it throws exceptions if the network is disconnected during a query.
Try:
var process = new Process
{
StartInfo =
{
FileName = "netsh.exe",
Arguments = "wlan show interfaces",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
var output = process.StandardOutput.ReadToEnd();
var line = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(l => l.Contains("SSID") && !l.Contains("BSSID"));
if (line == null)
{
return string.Empty;
}
var ssid = line.Split(new[] { ":" }, StringSplitOptions.RemoveEmptyEntries)[1].TrimStart();
return ssid;
It looks like this will do what you want:
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\WMI",
"SELECT * FROM MSNdis_80211_ServiceSetIdentifier");
foreach (ManagementObject queryObj in searcher.Get())
{
Console.WriteLine("-----------------------------------");
Console.WriteLine("MSNdis_80211_ServiceSetIdentifier instance");
Console.WriteLine("-----------------------------------");
if(queryObj["Ndis80211SsId"] == null)
Console.WriteLine("Ndis80211SsId: {0}",queryObj["Ndis80211SsId"]);
else
{
Byte[] arrNdis80211SsId = (Byte[])
(queryObj["Ndis80211SsId"]);
foreach (Byte arrValue in arrNdis80211SsId)
{
Console.WriteLine("Ndis80211SsId: {0}", arrValue);
}
}
}
from http://bytes.com/groups/net-c/657473-wmi-wifi-discovery
there is some more information in How do I get the available wifi APs and their signal strength in .net?
(cross-posted in How to get currently connected wifi SSID in c# using WMI or System.Net.NetworkInformation windows 10?)
I found a rather old library dating back to 2014:
Microsoft.WindowsAPICodePack-Core version 1.1.0.2
Although it is not conforming to .NET Standard, this library integrates with my .NET Core 3.0 app, but obviously is not cross-platform.
Sample code:
var networks = NetworkListManager.GetNetworks(NetworkConnectivityLevels.Connected);
foreach (var network in networks) {
sConnected = ((network.IsConnected == true) ? " (connected)" : " (disconnected)");
Console.WriteLine("Network : " + network.Name + " - Category : " + network.Category.ToString() + sConnected);
}
You are going to have to use native WLAN API. There is a long discussion about it here. Apparently this is what Managed Wifi API uses, so it will be easier for you to use it if you do not have any restrictions to use LGPL code.
I wanted to do exactly this, and tried using ManagedWifi, as suggested in other answers. But that led to unresolvable Exceptions as per here:
Issues with using Managed WiFi (NativeWiFi API)
I solved this by switching to using SimpleWiFi entirely and ignored the ManagedWifi package.
Glancing at the source code, it looks like SW is a fixed reimplementation of some of the functionality in MW.

How can I programmatically run the ASP.Net Development Server using C#?

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.

Categories