I'm trying to enumerates all open File Explorer windows in a PowerShell script.
I have already found on other posts how to enumerate all explorer.exe windows instances, for example using the Shell.Application COM API:
(New-Object -com "Shell.Application").windows()
But this actually returns more than I want:
I want only the "real" File Explorer windows showing actual files on my disk or network, not the "fake" explorer.exe instances that are just containers for various Control Panel windows, etc.
So basically the list of instances shown when hovering the mouse over the File Explorer icon on the Taskbar.
How can this be done reliably, and preferably in a way that works in Windows 7 to 11?
Comparing the window title to known strings like "Control Panel" or "Windows Update" has limited value. This would only eliminate the most common cases, and on English versions of Windows only.
I tried looking at the File Explorer window class, but it's "CabinetWClass" in all cases, even for Control Panels.
I noticed that real instances have a child window of class "UIRibbonWorkPane", whereas the Control Panel does not. But the ribbon can be disabled, so this is not a reliable marker.
My script already contains C# declarations encapsulating WIN32 API calls, so C# code snippets would also do.
2021-10-10 update:
The best algorithm I've found so far, building on #simon-mourier's answer, can summarized this way:
$self = $window.Document.Folder.Self
$ClassID = $Self.ExtendedProperty("System.NamespaceCLSID")
$BaseClassID = $Self.Path.Substring(2,38) # With proper tests to clear it if it's not a UUID
$FileExplorerIDs = ( # The few known types which are file systems, but don't set $Self.IsFileSystem
# Windows 10
"f02c1a0d-be21-4350-88b0-7367fc96ef3c", # Network
"679f85cb-0220-4080-b29b-5540cc05aab6", # Quick Access
"20d04fe0-3aea-1069-a2d8-08002b30309d", # This PC
# Windows 7
"031e4825-7b94-4dc3-b131-e946b44c8dd5" # Libraries
)
if ($Self.IsFileSystem) {
$AppType = "File Explorer"
} elseif ($FileExplorerIDs -contains "$ClassID") {
$AppType = "File Explorer"
} elseif ($BaseClassID -eq "{26EE0668-A00A-44D7-9371-BEB064C98683}") {
$AppType = "Control Panel"
} elseif ("{$ClassID}" -eq "{D20EA4E1-3957-11D2-A40B-0C5020524153}") {
$AppType = "Control Panel" # Windows 7 Administrative Tools
} elseif ($Self.Name -eq $Self.Path) { # TODO: Improve this test, which is very weak
$AppType = "Search Results" # Ex: "Search Results in Indexed Locations"
} else {
$AppType = "Unknown"
}
The full algorithm, with the proper precautions to eliminate undefined fields, or invalid values, etc, is implemented in this script:
https://github.com/JFLarvoire/SysToolsLib/blob/master/PowerShell/ShellApp.ps1
One solution is to test whether the Shell Folder (IShellFolder) beneath the Shell View that Windows sends back is handled by the Windows file system or by some custom folder.
For that, you can use the System.NamespaceCLSID Windows property. If the folder associated with the view is handled by the file system, this property value will be the ShellFSFolder GUID value which equal to f3364ba0-65b9-11ce-a9ba-00aa004ae837 (from Windows SDK shobjidl_core.h).
You can test it with something like this in PowerShell:
$ShellFSFolder = [System.Guid]::New("f3364ba0-65b9-11ce-a9ba-00aa004ae837")
foreach($win in (New-Object -com "Shell.Application").Windows()) {
$clsid = $win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID")
if ($clsid -ne $null) {
$clsid = [System.Guid]::New($clsid)
if ($clsid -eq $ShellFSFolder) {
Write-Host $win.Document.Folder.Self.Path
}
}
}
And like this in C#:
var ShellFSFolder = new Guid("f3364ba0-65b9-11ce-a9ba-00aa004ae837");
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
foreach (var win in shell.Windows)
{
var clsid = win.Document.Folder.Self.ExtendedProperty("System.NamespaceCLSID");
if (clsid != null)
{
Guid guid;
if (clsid is byte[] bytes)
{
guid = new Guid(bytes);
}
else
{
guid = new Guid((string)clsid);
}
if (guid == ShellFSFolder)
{
Console.WriteLine(win.Document.Folder.Title); // for example
}
}
}
It seems that only file-path-based File Explorer windows have a non-$null .LocationUrl property value, so you can filter by that:
Caveat: Jean-François reports that this approach doesn't work for Explorer windows that are open to a file-system folder located on a connected smartphone, in which case .LocationUrl is apparently $null too.
$explorerWinsWithFilePaths =
(New-Object -com "Shell.Application").Windows() | Where-Object LocationUrl
To extract the file paths that these windows are displaying (the technique also works with non-file locations such as Quick Access, which translate into ::-prefixed GUIDs):
$explorerWinsWithFilePaths.Document.Folder.Self.Path
See Jean-François' comment below for examples of what windows showing folders on a connected smartphone report.
Related
since some time now I try to figure out how to correctly setup this new UWF (Unified Write Filter). Unfortunately it seems there is only documentation for Win 8.1 industry (here), not for Win 10. I hope there were no relevant changes since.
I also asked this on the WindowsDevCenter but got no response so far.
Here is my problem:
With the WMI providers I got UWF enabled by now (UWF_Filter.Enable()), but I cannot protect any volume.
Also the volume list looks very strange: There are 4 entrys, everyone is with CurrentSession=True.
The first is for an volume with no drive letter, only a volume id.
The second is for C:
and then there are 2 identical for D: .
Should'nt there normally be 2 entrys per volume, one where CurrentSession is true and one where its false, meaning its the setting applied after reboot?
If I try to execute Protect on the ManagementObject with DriveLetter=C: I get an Access denied exception, I assume because its the object for the current session.
Also if I try uwfmgr.exe Volume Protect C: on the console it simply hangs: no reaction, no error, only a forever blinking cursor. EDIT: it turned out this was a problem caused by another installed software. See also below.
Do I have to enable or disable or do anything else before I can protect volumes?
Thanks in advance,
Sebastian
My system:
Windows 10 IOT Enterprise 2016 LTSB x64
1 SSD 250GB with Boot, C: and D:
Edit:
Here I asked a follow up question with some other details and a workaround. If I use uwfmgr.exe volume protect c: for example, it works and UWF_Volume now suddenly has (the correct) 2 entries for C:, one for the current and one for the next session.
However I want to avoid this, because IMHO it should be solveable by WMI only.
Edit 2: #sommmen
The partition layout is as following: One disk with 4 partitions.
Boot, 500MB
C:/ , 45GB
unknown, 500MB (Boot-Backup I think)
D:/ , ~200GB
PS:
Please could anyone create the tags uwf and uwfmgr? Would be nice :-)
Missing UWF_Volume instances often appeared after reboot in my tests. But if not, you can create them directly using ManagementClass.CreateInstance().
The problem here is that the official docs are not exactly correct. The description of the UWF_Volume.VolumeName property is:
The unique identifier of the volume on the current system. The
VolumeName is the same as the DeviceID property of the Win32_Volume
class for the volume.
from: https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume#properties
In fact, the DeviceID needs a slight modification, before using it as value for UWF_Volume.VolumeName:
DeviceID.Substring(4).TrimEnd('\\')
So, after removing prefix \\?\ and removing any trailing slashes you can create instances with CurrentSession=false for the specified device.
This also works in Windows 10 Pro without any uwfmgr.exe. Though, officially not recommended/supported.
Also, I was not able to delete instances, yet. So be sure to add only correct values.
Full Example:
// example value
var DeviceId_From_Win32_Volume = #"\\?\Volume{c2eac053-27e3-4f94-b28c-c2c53d5f4fe1}\";
// example value
var myDriveLetter = "C:";
var myDeviceId = DeviceId_From_Win32_Volume.Substring(4).TrimEnd('\\');
var wmiNamespace = "root\\standardcimv2\\embedded";
var className = "UWF_Volume";
var mgmtScope = new ManagementScope {Path = {NamespacePath = wmiNamespace}};
var mgmtPath = new ManagementPath(className);
var mgmtClass = new ManagementClass(mgmtScope, mgmtPath, null);
// prepare the new object
var newObj = mgmtClass.CreateInstance();
newObj.SetPropertyValue("DriveLetter", myDriveLetter);
newObj.SetPropertyValue("VolumeName", myDeviceId);
newObj.SetPropertyValue("CurrentSession", false);
newObj.SetPropertyValue("CommitPending", false);
newObj.SetPropertyValue("BindByDriveLetter", false);
// create the WMI instance
newObj.Put(new PutOptions {Type = PutType.CreateOnly});
I experience the similar issue in that I could not query the UWF_Volume with CurrentSession=False. However, there's one thing I did that seems to "generate" the UWF_Volume management object with CurrentSession=False. I ran "uwfmgr volume protect c:". Unfortunately, in your case running this causes it to hang.
Could you try running uwfmgr in cmd in admin? Also, if you run "uwfmgr get-config", would you be able to get the current setting of the write filter?
Another thing from your description: you said there are two identical volumes for D:, but if you looks closely at the properties, one would be CurrentSession=True, and the other one is CurrentSession=False. According to the documentation, if you want to make change, you must select the management object (UWF_Volume) with CurrentSession=False.
https://learn.microsoft.com/en-us/windows-hardware/customize/enterprise/uwf-volume
(scroll down to powershell script code sample section)
First of all a volume may have several partitions. They will show up as having the same drive label.
e.g.
C:/ //?/{some guid here}
C:/ //?/{some other guid here}
Now this is common for the %systemDrive% because this has the boot partition.
You can use the commands
mountvol
and
Diskpart
List volume
To figure out the right guid for your need (or you can protect both the boot partition and the system partition). Also using wmi you can look at Win32_volume under namespace cimv2 to get some more insight.
The command line util UWFmgr seems to create an UWF_VOLUME wmi instance once you run the protect command. The docs also hint that you need to create an object yourself.
function Set-ProtectVolume($driveLetter, [bool] $enabled) {
# Each volume has two entries in UWF_Volume, one for the current session and one for the next session after a restart
# You can only change the protection status of a drive for the next session
$nextConfig = Get-WMIObject -class UWF_Volume #CommonParams |
where {
$_.DriveLetter -eq "$driveLetter" -and $_.CurrentSession -eq $false
};
# If a volume entry is found for the drive letter, enable or disable protection based on the $enabled parameter
if ($nextConfig) {
Write-Host "Setting drive protection on $driveLetter to $enabled"
if ($Enabled -eq $true) {
$nextConfig.Protect() | Out-Null;
} else {
$nextConfig.Unprotect() | Out-Null;
}
}
=======> (!) im talking about this comment
# If the drive letter does not match a volume, create a new UWF_volume instance
else {
Write-Host "Error: Could not find $driveLetter. Protection is not enabled."
}
}
The docs however do not provide a method of doing this. For now it seems we need to use the command line util till someone has an example using the WMI provider.
To answer my own question: So far I have only a workaround but no real solution.
It is to check if there is an entry with CurrentSession=False and if not invoke the command directly:
ManagementObjectSearcher ms = new ManagementObjectSearcher(_Scope, new ObjectQuery("select * from UWF_Volume where VolumeName = \"" + volId + "\" AND CurrentSession=\"False\""));
ManagementObjectCollection c = ms.Get();
UInt32 res = 1;
foreach (ManagementObject mo in c)
{
// entry found: do it with WMI
res = (UInt32)mo.InvokeMethod(newState ? "Protect" : "Unprotect", new object[] { });
}
if (c.Count == 1 && res == 0)
// message: success
if (c.Count == 0)
{
// no entry found: invoke cmd
ProcessStartInfo info = new ProcessStartInfo("uwfmgr.exe", "volume " + (newState ? "Protect" : "Unprotect") + #" \\?\" + volId);
Process process = new Process();
info.Verb = "runas"; //needs admin
process.StartInfo = info;
process.Start();
process.WaitForExit();
}
This has the side effect that for a split second a command line window will pop up, but nevertheless it works well.
I want to open a file's location and select the file in explorer on Mac, Ubuntu from MonoDevelop.
This code is working on Windows (but it is not working on Mac and Ubuntu):
System.Diagnostics.Process.Start("explorer.exe", "/select, " + fileaddress);
Dim dir_path As String = "/media/os/test"
' Windows path example: dir_path = "C:\test"
Process.Start("file://" & dir_path)
Tested and worked on Ubuntu and Windows XP.
Source: http://www.stevenbrown.ca/blog/archives/156
By 2020-10, in mono 6.10, the above method didn't work on Ubuntu 20.04. The below approach solved the problem.
System.Diagnostics.Process.Start("mimeopen", "/var/tmp");
You can use 'open' on Mac, like this
System.Diagnostics.Process.Start("open", $"-R \"{File_Path_You_Wanna_Select}\"");
Here -R means reveal, to select in the Finder instead of opening.
To find more usage for open, just type open in terminal.
Using Process.Start() you bypass the .NET framework and move into the platform you're running onto, executing an arbitrary process.
On Windows you want to open the Windows Explorer, on Mac you want to open Finder and on Ubuntu it's simply called File Browser.
There is no Environment.OpenFileBrowser(string path) method in the framework, so you will have to let your program determine which platform it is running on, and open the approperiate file viewer.
See How to check the OS version at runtime e.g. windows or linux without using a conditional compilation statement to perform the former.
You are calling an OS specific (Windows) method. That won't work cross-platform.
Try the following inside a function/method:
Example - inside click event:
protected void OnOpen (object sender, EventArgs e)
{
using(FileChooserDialog chooser =
new FileChooserDialog(null,
"Select document to open...",
null,
FileChooserAction.Open,
"Open Selected File",
ResponseType.Accept,
"Discard & Return to Main Page",
ResponseType.Cancel))
{
if (chooser.Run () == (int)ResponseType.Accept)
{
System.IO.StreamReader file = System.IO.File.OpenText (chooser.Filename);
/* Copy the contents to editableTxtView <- This is the Widget Name */
editableTxtView.Buffer.Text = file.ReadToEnd ();
/* If you want to read the file into explorer, thunar, Notepad, etc.,
* you'll have to research that yourself. */
//Close file - - KEEP IT CLEAN - - & deAllocated memory!!
file.Close ();
}
}
}
The file has now been copied into an editable (Default) or read only (set in properties pad) textviewer Gtk widget. From there you should be able to manipulate it as you so choose.
I'm using code from this post over at Microsoft's TechNet to change the icon of my running PowerShell application. This works great for the icon that is displayed in the Powershell window itself, but it doesn't change the Taskbar's icon. I changed the function a bit an hoped that it would also change the icon displayed in the Taskbar.
# Set the icon of the current console window to the specified icon.
#
# AUTHOR: Aaron Lerch <http://www.aaronlerch.com/blog>
# COPYRIGHT: © 2009 Aaron Lerch
# LINK: http://gallery.technet.microsoft.com/scriptcenter/9d476461-899f-4c98-9d63-03b99596c2c3
#
# PARAM:
# -IconFile
# Absolute path to the icon file.
# RETURN:
# $null
function Set-ConsoleIcon {
Param(
[parameter(Mandatory = $true)] [string] $IconFile
)
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | Out-Null
# Verify the file exists
if ([System.IO.File]::Exists($iconFile) -eq $true) {
$ch = Invoke-Win32 'kernel32' ([IntPtr]) 'GetConsoleWindow'
$i = 0;
$size = 16;
while ($i -ne 4) {
$ico = New-Object System.Drawing.Icon($iconFile, $size, $size)
if ($ico -ne $null) {
Send-Message $ch 0x80 $i $ico.Handle | Out-Null
}
if ($i -eq 4) {
break
}
$i += 1
$size += 16
}
}
else {
Write-Host 'Icon file not found' -ForegroundColor 'Red'
}
}
I'm providing the icon in the sizes 16 (wParam 1), 32 (wParam 2), 48 (wParam 3), and 64 (wParam 4).
I also tried to change the Icon from my launching C# application (based on this Stackoverflow discussion) but that didn't work at all.
If you'd like to see the complete code have a look at the following:
the C# class that starts the Powershell process
the Powershell PS1 script
This might not be possible. Here are more details about the "group icon" in the taskbar:
Change icon of group in taskbar (Win7)
Update:
You can change the application ID of your window. Since the icon primarily comes from the application ID, by changing it Explorer doesn't know the default icon anymore and will use the actual window icon. This also ungroups the window from other CMD windows to make the individual icon visible at all. (There's a taskbar animation like for closed/new windows when you do this in an existing console window.) There's an MSDN article, look for "application ID" in it:
https://msdn.microsoft.com/en-us/magazine/dd942846.aspx
Here's the relevant code from it (C++):
#define WINVER 0x601
#define _WIN32_WINNT 0x601
#include <Propvarutil.h>
#include <propkey.h>
#include <Shellapi.h>
PROPVARIANT pv;
InitPropVariantFromString(L"MyAppID", &pv);
IPropertyStore* pps;
VERIFY(SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps)));
VERIFY(pps->SetValue(PKEY_AppUserModel_ID, pv));
VERIFY(pps->Commit());
(Linked libs: shlwapi.lib)
The Windows API Code Pack should also have managed wrapper code for this. Didn't look it up because I currently use this function in a C++ application. But I found other questions about it here.
For your PowerShell script that probably won't help much either. Since it's all native code wrapped with more complex managed code, I think your best bet would be a little native helper tool. I am currently integrating this function into my FlashConsoleWindow tool that can do some more things to console windows like flashing or displaying a taskbar progress state.
An easy alternative is to create a shortcut to the powershell exe. Change the icon of the shortcut to whatever you want.
Then whenever you call your script, use the shortcut instead of the PS exe. So instead of
powershell.exe -ExecutionPolicy Bypass -File D:\scripts\whatever.ps1
Use
D:\scripts\powershell.lnk -ExecutionPolicy Bypass -File D:\scripts\whatever.ps1
The shortcut powershell.lnk can be "C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe" or just powershell.exe without the full path (as long as PATH var is set properly for PS on the system)
You can put the PS shortcut anywhere really, but I would recommend you put it with the script for portability.
Now when you launch the script via the shortcut, the taskbar icon will be whatever you defined it as via the shortcut file.
The only caveat is that your window settings in the shortcut will override the PS window, so you should define how you want it to look in the shortcut properties.
I am trying to programmatically create and destroy a network bridge on Windows 7. Technologically I would love to stay within the .Net 4 realm (PInvokes are fine, ofc), but utilizing C++ is an option.
My research so far turned up that for configuration, netsh-commands are the route to go. However, there seems to be no option to actually spin up a new bridge with them.
I am currently investigating this program that uses the INetCfg APIs, but it appears that the program or, more specifically, the APIs, are not able to (again) build a new bridge.
If anyone can contribute to solving the problem, any kind of help is greatly appreciated.
[Update:] It seems that newtork bridges are implemented using a driver which then binds to both devices. I cannot yet make much of that information, so still any help is appreciated.
I've found a solution that works for both bridge service and bridge adapter driver. I don't use UpdateDriverForPlugAndPlayDevices like devcon but I'm using DiInstallDevice instead.
However, installing the drivers for the first time in non interactive mode (without user interaction) is not possible. This is because there are no corresponding .cat files for the builtin bridge .inf files. Neither UpdateDriverForPlugAndPlayDevices nor DiInstallDevice nor DiInstallDriver is intended for manual driver installation where .inf file is already contained in %SystemRoot%\inf but not yet in %SystemRoot%\System32\DriverStore.
The files should be on the distribution media or in a vendor-created directory, not in a system location such as %SystemRoot%\inf
All of the mentioned installation methods will create a OEM copy of the .inf file and will install it in driver store. Because this OEM copy is initially not part of the driver store, windows will show a prompt dialog and ask for user interaction either force installing the driver or canceling. Subsequent driver installations is possible without any user interaction by the way. Also preinstalled drivers (see pnputil -a) can be installed in non interactive mode.
So this is my solution:
First a device entry in HKLM\System\CurrentControlSet\Enum\Root is created with the given hardware id as device name (ms_bridge, ms_bridgemp) with the help of SetupDiCreateDeviceInfo
The hardware id is assigned with SetupDiSetDeviceRegistryProperty
The driver list is builded by exactly the given single .inf file with the help of SetupDiSetDeviceInstallParams
Enumerating and preselecting driver with SetupDiSetSelectedDriver
Registering device with SetupDiCallClassInstaller(DIF_REGISTERDEVICE...)
Installing with DiInstallDevice
This is the full code:
HRESULT InstallDriver(const wchar_t* DriverInfFile, const wchar_t* HardwareId) {
HRESULT Hr = S_OK;
GUID ClassGUID;
wchar_t ClassName[MAX_CLASS_NAME_LEN] = {0};
if (SetupDiGetINFClass(DriverInfFile, &ClassGUID, ClassName, sizeof(ClassName) / sizeof(wchar_t), nullptr) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
return Hr;
}
HDEVINFO DeviceInfoSet = SetupDiCreateDeviceInfoList(&ClassGUID, nullptr);
if (DeviceInfoSet == INVALID_HANDLE_VALUE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
return Hr;
}
SP_DEVINFO_DATA DeviceInfoData = {
sizeof(SP_DEVINFO_DATA), 0
};
if (SetupDiCreateDeviceInfo(DeviceInfoSet, HardwareId, &ClassGUID, nullptr, nullptr, DICD_GENERATE_ID, &DeviceInfoData) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return Hr;
}
if (SetupDiSetDeviceRegistryProperty(DeviceInfoSet, &DeviceInfoData, SPDRP_HARDWAREID, (LPBYTE) HardwareId, (DWORD) (wcslen(HardwareId) + 1) * sizeof(wchar_t)) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return Hr;
}
SP_DEVINSTALL_PARAMS InstallParams = {sizeof(SP_DEVINSTALL_PARAMS), 0};
InstallParams.FlagsEx = DI_FLAGSEX_ALLOWEXCLUDEDDRVS | DI_FLAGSEX_ALWAYSWRITEIDS;
InstallParams.Flags = DI_QUIETINSTALL | DI_ENUMSINGLEINF;
wcscpy_s(InstallParams.DriverPath, DriverInfFile);
if (SetupDiSetDeviceInstallParams(DeviceInfoSet, &DeviceInfoData, &InstallParams) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return Hr;
}
SP_DRVINFO_DATA DriverInfoData = {sizeof(SP_DRVINFO_DATA), 0};
if (SetupDiBuildDriverInfoList(DeviceInfoSet, &DeviceInfoData, SPDIT_COMPATDRIVER) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
SetupDiDestroyDriverInfoList(DeviceInfoSet, &DeviceInfoData, SPDIT_COMPATDRIVER);
}
// Use first best driver (since specified by inf file)
if (SetupDiEnumDriverInfo(DeviceInfoSet, &DeviceInfoData, SPDIT_COMPATDRIVER, 0, &DriverInfoData)) {
SetupDiSetSelectedDriver(DeviceInfoSet, &DeviceInfoData, &DriverInfoData);
}
if (SetupDiCallClassInstaller(DIF_REGISTERDEVICE, DeviceInfoSet, &DeviceInfoData) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
}
// TODO: Allow non interactive mode for drivers already contained in %SystemRoot%\inf directory
//BOOL PreviousMode = SetupSetNonInteractiveMode(TRUE);
if (Hr == S_OK) {
if (DiInstallDevice(nullptr, DeviceInfoSet, &DeviceInfoData, &DriverInfoData, 0, nullptr) == FALSE) {
Hr = HRESULT_FROM_SETUPAPI(GetLastError());
// Ensure that the device entry in \ROOT\ENUM\ will be removed...
SetupDiRemoveDevice(DeviceInfoSet, &DeviceInfoData);
}
}
//SetupSetNonInteractiveMode(PreviousMode);
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return Hr;
}
Todo's: Find a way to install this bridge drivers from within %SystemRoot%\inf without creating OEM copies and without any user interaction.
You can gain read/write access to subversion repository at Sourceforge
Any additional information or suggestion for improvement is appreciated! Everyone please feel free to checkout/modify the code.
Basic commands:
bridgeutil.exe /install
bridgeutil.exe /uninstall
bridgeutil.exe /attach
bridgeutil.exe /detach
Examples:
bridgeutil.exe /attach "PCI\VEN_10EC&DEV_8169" /attach {5d624f94-8850-40c3-a3fa-a4fd2080baf3}\vwifimp
Attaches each Realtek 8169 Network Interface Cards and Microsoft Virtual Wifi Adapter to bridge. If the bridge is not installed yet, it will be installed first.
bridgeutil.exe /detach 1
Detaches adapter with id 1 from bridge.
To see a list of bridgeable adapters, just call bridgeutil.exe without any arguments.
It is actually possible to create and network bridges via the SetupAPI.
Using the DevCon Tool, destroying them is as easy as this...
devcon.exe remove ms_bridgemp
...while building bridges can be done with this command:
devcon.exe install "C:\Windows\inf\netbrdgm.inf" ms_bridgemp
DevCon is open source, so you can dig into the sources to see how it implements those commands (the DevCon Tool is essentially a CLI to the SetupAPI).
Please note: The commands relate to Windows 7. The approach is said to work on XP and I suppose it works on other Windows Versions, too, but the .INF-File might have a different name or the device ID might differ.
After much unsuccessful searching on the Internet I wrote and have been successfully using the following Windows Script Host script "BridgeConnections.vbs" to create a network bridge on Windows XP (this method also works on Windows 7 and Windows 8 with slight modifications). It can be run from the command prompt or from a batch file as follows:
C:\Temp> cscript BridgeConnections.vbs
File BridgeConnections.vbs:
' This VBScript opens the "Network Connections" control panel window,
' sends Ctrl+A ("Select All") and Alt+N ("Advanced" menu) and
' C ("Bridge Connections" menu command) keystrokes to it and then waits
' until the splash window "Please wait while Windows bridges the connections..."
' disappears from the screen
Dim WshShell, Count
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Exec("rundll32.exe shell32.dll,Control_RunDLL ncpa.cpl")
Count = 0
Do While Not WshShell.AppActivate("Network Connections") And Count < 10
Count = Count + 1
WScript.Sleep 1000
WScript.Echo "Waiting for the 'Network Connections' window... " & CStr(Count) & "s"
Loop
WshShell.SendKeys "^(a)"
WshShell.SendKeys "%(n)"
WshShell.SendKeys "c"
Count = 0
Do While Not WshShell.AppActivate("Network Bridge") And Count < 10
Count = Count + 1
WScript.Sleep 1000
WScript.Echo "Waiting for the 'Network Bridge' splash window... " & CStr(Count) & "s"
Loop
Count = 0
Do While WshShell.AppActivate("Network Bridge") And Count < 120
Count = Count + 1
WScript.Sleep 1000
WScript.Echo "Waiting for the 'Network Bridge' splash window to disappear... " & CStr(Count) & "s"
Loop
Likewise one could modify the script to "Delete" the bridge if required (make a single selection with Shift and navigate keys and send a different keystroke command). In my case I only need to bridge all available ethernet adapters from a batch file so the above method works just fine.
In my experience, the "slight" problem with the
devcon.exe install "C:\Windows\inf\netbrdgm.inf" ms_bridgemp
approach posted here earlier is that it would create an empty, "half-backed" bridge with no adapters in it. So you will still have to go to the Windows GUI and "Add" adapters it one by one manually before it becomes really usable.
The only fully automated solution that really works for me it the above script.
To do the same actions from a C++ or C# code without the script you'd need to know and call undocumented Shell Network Interfaces (NETSHELL.DLL) functions which in turn are called by the Explorer Shell when the user initiates the actions via list view item selection and context menu command in the Windows GUI. A C++ sample of calling into the Shell Network Interface for programmatically disabling/enabling a Network Adapter can be seen here. Unfortunately there is no sample yet for creating/removing the Network Bridge adapter. So until it becomes available I'll stick with the script.
Based on the bindview example, I put up an utilitary called bindbridge, which works as following:
Usage: bindbridge <deviceId> <bind|unbind>
The source can be found at https://github.com/OurGrid/OurVirt/tree/master/tools/win32/bindbridge, and it assumes the bridge device already exists - which can be created with devcon, as per previous answers - and its name to be ms_bridge, what can be easily changed in the sources.
I'm using it to programatically add tap interfaces to the bridge, so my command line is something in the lines of:
bindbridge ROOT\NET\0001 bind
It turns out that, unfortunately, there is no documented way of setting up a network bridge.
The code which does that is located inside hnetcfg.dll, and is invoked only by Windows Explorer. It installs bridge driver, and configures bridge interface.
It might be possible to call it yourself (using COM), but that would require reverse engineering and may break on any system update, so I recommend against doing that.
I'm building a web application in which I need to scan the user-uploaded files for viruses.
Does anyone with experience in building something like this can provide information on how to get this up and running? I'm guessing antivirus software packages have APIs to access their functionality programatically, but it seems it's not easy to get a hand on the details.
FYI, the application is written in C#.
Important note before use:
Be aware of TOS agreement. You give them full access to everything: "When you upload or otherwise submit content, you give VirusTotal (and those we work with) a worldwide, royalty free, irrevocable and transferable licence to use, edit, host, store, reproduce, modify, create derivative works, communicate, publish, publicly perform, publicly display and distribute such content."
Instead of using a local Antivirus program (and thus binding your program to that particular Antivirus product and requesting your customers to install that Antivirus product) you could use the services of VirusTotal.com
This site provides a free service in which your file is given as input to numerous antivirus products and you receive back a detailed report with the evidences resulting from the scanning process. In this way your solution is no more binded to a particular Antivirus product (albeit you are binded to Internet availability)
The site provides also an Application Programming Interface that allows a programmatically approach to its scanning engine.
Here a VirusTotal.NET a library for this API
Here the comprensive documentation about their API
Here the documentation with examples in Python of their interface
And because no answer is complete without code, this is taken directly from the sample client shipped with the VirusTotal.NET library
static void Main(string[] args)
{
VirusTotal virusTotal = new VirusTotal(ConfigurationManager.AppSettings["ApiKey"]);
//Use HTTPS instead of HTTP
virusTotal.UseTLS = true;
//Create the EICAR test virus. See http://www.eicar.org/86-0-Intended-use.html
FileInfo fileInfo = new FileInfo("EICAR.txt");
File.WriteAllText(fileInfo.FullName, #"X5O!P%#AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
//Check if the file has been scanned before.
FileReport fileReport = virusTotal.GetFileReport(fileInfo);
bool hasFileBeenScannedBefore = fileReport.ResponseCode == ReportResponseCode.Present;
Console.WriteLine("File has been scanned before: " + (hasFileBeenScannedBefore ? "Yes" : "No"));
//If the file has been scanned before, the results are embedded inside the report.
if (hasFileBeenScannedBefore)
{
PrintScan(fileReport);
}
else
{
ScanResult fileResult = virusTotal.ScanFile(fileInfo);
PrintScan(fileResult);
}
... continue with testing a web site ....
}
DISCLAIMER
I am in no way involved with them. I am writing this answer just because it seems to be a good update for these 4 years old answers.
You can use IAttachmentExecute API.
Windows OS provide the common API to calling the anti virus software which is installed (Of course, the anti virus software required support the API).
But, the API to calling the anti virus software provide only COM Interface style, not supported IDispatch.
So, calling this API is too difficult from any .NET language and script language.
Download this library from here Anti Virus Scanner for .NET or add reference your VS project from "NuGet" AntiVirusScanner
For example bellow code scan a file :
var scanner = new AntiVirus.Scanner();
var result = scanner.ScanAndClean(#"c:\some\file\path.txt");
Console.WriteLine(result); // console output is "VirusNotFound".
I would probably just make a system call to run an independent process to do the scan. There are a number of command-line AV engines out there from various vendors.
Take a look at the Microsoft Antivirus API. It makes use of COM, which should be easy enough to interface with from .NET. It refers specifically to Internet Explorer and Microsoft Office, but I don't see why you wouldn't be able to use to to on-demand scan any file.
All modern scanners that run on Windows should understand this API.
Various Virus scanners do have API's. One I have integrated with is Sophos. I am pretty sure Norton has an API also while McAfee doesn't (it used to). What virus software do you want to use? You may want to check out Metascan as it will allow integration with many different scanners, but there is an annual license cost. :-P
I also had this requirement. I used clamAv anti virus which provides on-demand scanning by sending the file to their tcp listening port. You can use nClam nuget package to send files to clamav.
var clam = new ClamClient("localhost", 3310);
var scanResult = clam.ScanFileOnServerAsync("C:\\test.txt"); //any file you would like!
switch (scanResult.Result.Result)
{
case ClamScanResults.Clean:
Console.WriteLine("The file is clean!");
break;
case ClamScanResults.VirusDetected:
Console.WriteLine("Virus Found!");
Console.WriteLine("Virus name: {0}", scanResult.Result.InfectedFiles[0].FileName);
break;
case ClamScanResults.Error:
Console.WriteLine("Woah an error occured! Error: {0}", scanResult.Result.RawResult);
break;
}
A simple and detailed example is shown here. Note:- The synchronous scan method is not available in the latest nuget. You have to code like I done above
For testing a virus you can use the below string in a txt file
X5O!P%#AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
Shameless plug but you might want to check out https://scanii.com, it's basically malware/virus detection as a (REST) service. Oh also, make sure you read and understand virustotal's API terms (https://www.virustotal.com/en/documentation/public-api/) - they are very clear about not allowing commercial usage.
I would recommend using this approach:
using System;
using System.Diagnostics;
using Cloudmersive.APIClient.NET.VirusScan.Api;
using Cloudmersive.APIClient.NET.VirusScan.Client;
using Cloudmersive.APIClient.NET.VirusScan.Model;
namespace Example
{
public class ScanFileAdvancedExample
{
public void main()
{
// Configure API key authorization: Apikey
Configuration.Default.AddApiKey("Apikey", "YOUR_API_KEY");
var apiInstance = new ScanApi();
var inputFile = new System.IO.FileStream("C:\\temp\\inputfile", System.IO.FileMode.Open); // System.IO.Stream | Input file to perform the operation on.
var allowExecutables = true; // bool? | Set to false to block executable files (program code) from being allowed in the input file. Default is false (recommended). (optional)
var allowInvalidFiles = true; // bool? | Set to false to block invalid files, such as a PDF file that is not really a valid PDF file, or a Word Document that is not a valid Word Document. Default is false (recommended). (optional)
var allowScripts = true; // bool? | Set to false to block script files, such as a PHP files, Pythong scripts, and other malicious content or security threats that can be embedded in the file. Set to true to allow these file types. Default is false (recommended). (optional)
var allowPasswordProtectedFiles = true; // bool? | Set to false to block password protected and encrypted files, such as encrypted zip and rar files, and other files that seek to circumvent scanning through passwords. Set to true to allow these file types. Default is false (recommended). (optional)
var restrictFileTypes = restrictFileTypes_example; // string | Specify a restricted set of file formats to allow as clean as a comma-separated list of file formats, such as .pdf,.docx,.png would allow only PDF, PNG and Word document files. All files must pass content verification against this list of file formats, if they do not, then the result will be returned as CleanResult=false. Set restrictFileTypes parameter to null or empty string to disable; default is disabled. (optional)
try
{
// Advanced Scan a file for viruses
VirusScanAdvancedResult result = apiInstance.ScanFileAdvanced(inputFile, allowExecutables, allowInvalidFiles, allowScripts, allowPasswordProtectedFiles, restrictFileTypes);
Debug.WriteLine(result);
}
catch (Exception e)
{
Debug.Print("Exception when calling ScanApi.ScanFileAdvanced: " + e.Message );
}
}
}
}
Note that this way you can even control whether you filter out non-virus threat payloads such as executables, scripts, encrypted/password-protected files, etc.
This approach has a free tier and can also validate the contents of the files that you upload.
We tried two options:
clamav-daemon installed on a tiny linux container + "nClam" .NET library to interact with it. Works fine, but Clam AV misses a lot (a lot!) of viruses, especially dangerous macros hidden in MS Office files. Also ClamAV virus database has to be kept in memory at all times, which uses around 3.5GB of memory, which requires a rather expensive cloud virtual machine.
Ended up using Windows Defender via MpCmdRun.exe CLI api. See answer here
You can try to use DevDragon.io.
It is a web service with an API and .NET client DevDragon.Antivirus.Client you can get from NuGet. Scans are sub 200ms for 1MB file.
More documentation here:
https://github.com/Dev-Dragon/Antivirus-Client
Disclosure: I work for them.
From my experience you can use COM for interfacing with some anti-virus software. But what I would suggest is a bit easier, just parse scan results after scanning. All you need to do is to start the scanner process and point it to file/folder you want to scan, store scan results into file or redirect stdout to your application and parse results.
//Scan
string start = Console.ReadLine();
System.Diagnostics.Process scanprocess = new System.Diagnostics.Process();
sp.StartInfo.WorkingDirectory = #"<location of your antivirus>";
sp.StartInfo.UseShellExecute = false;
sp.StartInfo.FileName = "cmd.exe";
sp.StartInfo.Arguments = #"/c antivirusscanx.exe /scan="+filePath;
sp.StartInfo.CreateNoWindow = true;
sp.StartInfo.RedirectStandardInput = true;
sp.StartInfo.RedirectStandardError = true; sp.Start();
string output = sp.StandardOutput.ReadToEnd();
//Scan results
System.Diagnostics.Process pr = new System.Diagnostics.Process();
pr.StartInfo.FileName = "cmd.exe";
pr.StartInfo.Arguments = #"/c echo %ERRORLEVEL%";
pr.StartInfo.RedirectStandardInput = true;
pr.StartInfo.RedirectStandardError = true; pr.Start();
output = processresult.StandardOutput.ReadToEnd();
pr.Close();