Identify processes that have placed locks on a DLL using Powershell - c#

Using powershell, I want to identify any process locks placed a given DLL.
Solved. See below.

function IsDLLFree()
{
# The list of DLLs to check for locks by running processes.
$DllsToCheckForLocks = "C:\mydll1.dll","C:\mydll2.dll";
# Assume true, then check all process dependencies
$result = $true;
# Iterate through each process and check module dependencies
foreach ($p in Get-Process)
{
# Iterate through each dll used in a given process
foreach ($m in Get-Process -Name $p.ProcessName -Module -ErrorAction SilentlyContinue)
{
# Check if dll dependency matches any DLLs in list
foreach ($dll in $DllsToCheckForLocks)
{
# Compare the fully-qualified file paths,
# if there's a match then a lock exists.
if ( ($m.FileName.CompareTo($dll) -eq 0) )
{
$pName = $p.ProcessName.ToString()
Write-Error "$dll is locked by $pName. Stop this service to release this lock on $m1."
$result = $false;
}
}
}
}
return $result;
}

This works if you're assessing dll files loaded in the current application domain. If you pass in the path to the dll file it will return whether or not that assemblies is loaded in the current application domain. This is particularly useful even if you don't know the .dll file (still works for that), but want to know if a general area has .dll files with locks.
function Get-IsPathUsed()
{
param([string]$Path)
$isUsed = $false
[System.AppDomain]::CurrentDomain.GetAssemblies() |? {$_.Location -like "*$Path*"} |% {
$isUsed = $true;
}
$isUsed;
}

Related

How to enumerate the real Windows File Explorer windows

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.

Updating PATH environment variable via registry setting using C# causes path to no longer be valid

I have a folder that I want to add to the PATH variable under Environment Variables (for the machine). I am appending the folder to the path via the registry setting. SYSTEM\CurrentControlSet\Control\Session Manager\Environment.
Here is a snippet of the code where I read the registry setting. And I perform a registry update on the setting, so nothing revolutionary.
String keyName = #"SYSTEM\CurrentControlSet\Control\Session Manager\Environment\";
string existingPathFolderVariable = (string)Registry.LocalMachine.OpenSubKey(keyName).GetValue("PATH", "", RegistryValueOptions.DoNotExpandEnvironmentNames);
string keyValue = #"c:\MyPath\";
if ( !existingPathFolderVariable.Contains(keyValue) )
{
if (!existingPathFolderVariable.EndsWith(";", StringComparison.InvariantCulture))
{
existingPathFolderVariable += ';';
}
Followed by code to update registry value, standard registry functions.
}
I tried various options of updating the registry including using powershell.
$oldpath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).path
$newpath = "$oldpath;c:\install\sysinternals"
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $newPath
(Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
Though the path is updated and the values look correct, the path is no longer valid. The commands under the System32 folder are no longer valid. If I perform a ping, I get the unknown command message. Same for ipconfig and other commands.
I read that I could use the SetEnvironmentVariable function. But I do not want the values expanded.
If I copy the line, delete the line, and add the line via the registry setting or UI, the problem is resolved.
Any suggestions on how to resolve the problem?

How to check whether folder exists on multiple servers?

I have written below PowerShell script in one server to search for folder existence in other servers.
$Servers = Get-Content C:\scripts\serverlist.txt
foreach ($Server in $Servers) {
$Test = Test-Path -Path "\\$Server\c$\Documents and Settings\"
if ($Test -eq $true) {
Write-Host "Path exists on $Server."
} else {
Write-Host "Path NOT exist on $Server."
}
}
I am getting correct answer if I search for folders present in same server, but if I search for folders present in other server am getting "Path NOT exist on $Server" even though it is present.
Later I tried this one. With this also am facing the same issue
Get-Content c:\Users\jason\Documents\Scripts\Serverlist.txt |
Select-Object #{Name='ComputerName';Expression={$_}},
#{Name='FolderExist';Expression={Test-Path "\\$_\c$\program files\folder"}}
Also, please let me know if there is any method for this using C#.

How Do I Configure Changable Connection Strings in a Powershell Binary Module?

I have a PowerShell module I wrote in C# which using Entity Framework to access a MS SQL server. In my project I have created a module.psd1 file. This file specifies a script (LoadAppConfig.ps1) to run before loading the dll. The script reads app.config and adds the connection string to the current app domain.
Is there a better way to do this? I know some modules can take parameters when you load them, but I haven't been able to find a way to implement this in C#. Or, is there a way to embed a default value in the module, which can be changed by a cmdlet? I would like to avoid having to recompile the module to change data sources if possible.
LoadAppConfig.ps1
Add-Type -AssemblyName System.Configuration
$path = $PSScriptRoot
$configPath = Join-Path -Path $path -ChildPath "App.Config"
$connectionstrings = [System.Configuration.ConfigurationManager]::ConnectionStrings
$collection = [System.Configuration.ConfigurationElementCollection]
$field = $collection.GetField(
"bReadOnly",
[System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
$field.SetValue($connectionstrings, $false)
[xml]$xml = gc $configPath
$name = $xml.configuration.connectionStrings.add.name
$newconnectionstring = $xml.configuration.connectionStrings.add.connectionString
$provider = "System.Data.EntityClient"
if ($connectionstrings.name -notcontains $name)
{
$Entities = [System.Configuration.ConnectionStringSettings]::new($name, $newconnectionstring, $provider)
$connectionstrings.add($Entities)
}

Relative paths not working if batch file calls exe based on OS architecture

I have a requirement where I need to run a particular .exe based on the architecture.
My folder structure is like this:
The Tools folder looks like this
Under that the Binaries folder contains 2 sub folder (one each for 32/64 bit)
Each of these folders (x64/x32) looks like below:
The Root folder contains the .bat (start.bat) file that calls the appropriate exe (For example: if it is 32bit, it calls "\Binaries\x86\Tool.exe" or if it is 64bit, it calls "\Binaries\x64\Tool.exe".
The code in start.bat is as below:
#ECHO OFF
if exist "%SYSTEMDRIVE%\Program Files (x86)\" (
start "" /d "%~dp0" "Binaries\x86\Tool.exe"
) else (
start "" /d "%~dp0" "Binaries\x64\Tool.exe"
)
The calling is fine and it calls the particular .exe. The problem comes when the .exe application tries to use the XML file (each folder also contains a XML file, parameters.xml, along with exe) it throws an error. I am accessing the XML file using relative paths like ("./parameters.xml").
I tried recoding the code by using "System.AppDomain.CurrentDomain.BaseDirectory" (as this is a WPF exe). That works for relative path, but another scenario fails. I will explain the same below:
In the application i am getting the instances of SQL installed on the machine. To achieve that I am using the following code:
internal static List < string > SQLServerInstances() {
var sqlInstances = new List < string > ();
try {
using(RegistryKey sqlKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").
OpenSubKey(#
"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL")) {
if (sqlKey != null) {
foreach(string versionKeyName in sqlKey.GetValueNames()) {
sqlInstances.Add(versionKeyName);
}
}
}
} catch (Exception de) {
throw de;
}
}
This code returns no instances if I use the batch file to run the .exe. If I run the .exe directly from "Binaries\x64\Tool.exe", this code passes and returns me the SQL instances properly.
I am not sure what the issue is. This might be expected behaviour but seems a bit weird.
Try changing your start.bat code this way:
#ECHO OFF
cd "%~dp0"
if exist "%SYSTEMDRIVE%\Program Files (x86)\" (
start "" "Binaries\x86\Tool.exe"
) else (
start "" "Binaries\x64\Tool.exe"
)

Categories

Resources