Windows version of Linux's 'which'? [duplicate] - powershell

How do I ask PowerShell where something is?
For instance, "which notepad" and it returns the directory where the notepad.exe is run from according to the current paths.

The very first alias I made once I started customizing my profile in PowerShell was 'which'.
New-Alias which get-command
To add this to your profile, type this:
"`nNew-Alias which get-command" | add-content $profile
The `n at the start of the last line is to ensure it will start as a new line.

Here is an actual *nix equivalent, i.e. it gives *nix-style output.
Get-Command <your command> | Select-Object -ExpandProperty Definition
Just replace with whatever you're looking for.
PS C:\> Get-Command notepad.exe | Select-Object -ExpandProperty Definition
C:\Windows\system32\notepad.exe
When you add it to your profile, you will want to use a function rather than an alias because you can't use aliases with pipes:
function which($name)
{
Get-Command $name | Select-Object -ExpandProperty Definition
}
Now, when you reload your profile you can do this:
PS C:\> which notepad
C:\Windows\system32\notepad.exe

I usually just type:
gcm notepad
or
gcm note*
gcm is the default alias for Get-Command.
On my system, gcm note* outputs:
[27] » gcm note*
CommandType Name Definition
----------- ---- ----------
Application notepad.exe C:\WINDOWS\notepad.exe
Application notepad.exe C:\WINDOWS\system32\notepad.exe
Application Notepad2.exe C:\Utils\Notepad2.exe
Application Notepad2.ini C:\Utils\Notepad2.ini
You get the directory and the command that matches what you're looking for.

Try this example:
(Get-Command notepad.exe).Path

My proposition for the Which function:
function which($cmd) { get-command $cmd | % { $_.Path } }
PS C:\> which devcon
C:\local\code\bin\devcon.exe

A quick-and-dirty match to Unix which is
New-Alias which where.exe
But it returns multiple lines if they exist so then it becomes
function which {where.exe command | select -first 1}

I like Get-Command | Format-List, or shorter, using aliases for the two and only for powershell.exe:
gcm powershell | fl
You can find aliases like this:
alias -definition Format-List
Tab completion works with gcm.
To have tab list all options at once:
set-psreadlineoption -editmode emacs

This seems to do what you want (I found it on http://huddledmasses.org/powershell-find-path/):
Function Find-Path($Path, [switch]$All = $false, [Microsoft.PowerShell.Commands.TestPathType]$type = "Any")
## You could comment out the function stuff and use it as a script instead, with this line:
#param($Path, [switch]$All = $false, [Microsoft.PowerShell.Commands.TestPathType]$type = "Any")
if($(Test-Path $Path -Type $type)) {
return $path
} else {
[string[]]$paths = #($pwd);
$paths += "$pwd;$env:path".split(";")
$paths = Join-Path $paths $(Split-Path $Path -leaf) | ? { Test-Path $_ -Type $type }
if($paths.Length -gt 0) {
if($All) {
return $paths;
} else {
return $paths[0]
}
}
}
throw "Couldn't find a matching path of type $type"
}
Set-Alias find Find-Path

Check this PowerShell Which.
The code provided there suggests this:
($Env:Path).Split(";") | Get-ChildItem -filter notepad.exe

Try the where command on Windows 2003 or later (or Windows 2000/XP if you've installed a Resource Kit).
BTW, this received more answers in other questions:
Is there an equivalent of 'which' on Windows?
PowerShell equivalent to Unix which command?

If you want a comamnd that both accepts input from pipeline or as paramater, you should try this:
function which($name) {
if ($name) { $input = $name }
Get-Command $input | Select-Object -ExpandProperty Path
}
copy-paste the command to your profile (notepad $profile).
Examples:
❯ echo clang.exe | which
C:\Program Files\LLVM\bin\clang.exe
❯ which clang.exe
C:\Program Files\LLVM\bin\clang.exe

I have this which advanced function in my PowerShell profile:
function which {
<#
.SYNOPSIS
Identifies the source of a PowerShell command.
.DESCRIPTION
Identifies the source of a PowerShell command. External commands (Applications) are identified by the path to the executable
(which must be in the system PATH); cmdlets and functions are identified as such and the name of the module they are defined in
provided; aliases are expanded and the source of the alias definition is returned.
.INPUTS
No inputs; you cannot pipe data to this function.
.OUTPUTS
.PARAMETER Name
The name of the command to be identified.
.EXAMPLE
PS C:\Users\Smith\Documents> which Get-Command
Get-Command: Cmdlet in module Microsoft.PowerShell.Core
(Identifies type and source of command)
.EXAMPLE
PS C:\Users\Smith\Documents> which notepad
C:\WINDOWS\SYSTEM32\notepad.exe
(Indicates the full path of the executable)
#>
param(
[String]$name
)
$cmd = Get-Command $name
$redirect = $null
switch ($cmd.CommandType) {
"Alias" { "{0}: Alias for ({1})" -f $cmd.Name, (. { which $cmd.Definition } ) }
"Application" { $cmd.Source }
"Cmdlet" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"Function" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"Workflow" { "{0}: {1} {2}" -f $cmd.Name, $cmd.CommandType, (. { if ($cmd.Source.Length) { "in module {0}" -f $cmd.Source} else { "from unspecified source" } } ) }
"ExternalScript" { $cmd.Source }
default { $cmd }
}
}

Use:
function Which([string] $cmd) {
$path = (($Env:Path).Split(";") | Select -uniq | Where { $_.Length } | Where { Test-Path $_ } | Get-ChildItem -filter $cmd).FullName
if ($path) { $path.ToString() }
}
# Check if Chocolatey is installed
if (Which('cinst.bat')) {
Write-Host "yes"
} else {
Write-Host "no"
}
Or this version, calling the original where command.
This version also works better, because it is not limited to bat files:
function which([string] $cmd) {
$where = iex $(Join-Path $env:SystemRoot "System32\where.exe $cmd 2>&1")
$first = $($where -split '[\r\n]')
if ($first.getType().BaseType.Name -eq 'Array') {
$first = $first[0]
}
if (Test-Path $first) {
$first
}
}
# Check if Curl is installed
if (which('curl')) {
echo 'yes'
} else {
echo 'no'
}

You can install the which command from https://goprogram.co.uk/software/commands, along with all of the other UNIX commands.

If you have scoop you can install a direct clone of which:
scoop install which
which notepad

There also always the option of using which. there are actually three ways to access which from Windows powershell, the first (not necessarily the best) wsl -e which command (this requires installation of windows subsystem for Linux and a running distro). B. gnuwin32 which is a port of several gnu binaries in .exe format as standle alone bundled lanunchers option three, install msys2 (cross compiler platform) if you go where it installed in /usr/bin you'll find many many gnu utils that are more up-to-date. most of them work as stand alone exe and can be copied from the bin folder to your home drive somewhere amd added to your PATH.

There also always the option of using which. there are actually three ways to access which from Windows powershell
The first, (though not the best) is wsl(windows subsystem for linux)
wsl -e which command
This requires installation of windows subsystem for Linux and a running distro.
Next is gnuwin32 which is a port of several gnu binaries in .exe format as standle alone bundled lanunchers
Third, install msys2 (cross compiler platform) if you go where it installed in /usr/bin you'll find many many gnu utils that are more up-to-date. most of them work as stand alone exe and can be copied from the bin folder to your home drive somewhere amd added to your PATH.

Related

Is there a way to find every single modules which will be needed in script?

I'd like to use a kinda analyzer which will install/import all the needed modules by the script before I run it on distant machine (which could not have it) ......
any idea ?
EDIT
Here's the case :
I'm on my dev machine, I'ved already installed lots of modules of all kind (dhcp, ntfs, remoting, register, etc.)
When I finally got my script (which is a function) to work, I can't be sure of what modules are used....
What I want is to write down, in the 'begin' section, the correct imports before I send my script on remote PCs; to be sure it's gonna run perfectly, you follow ?...
Is there a kinda a third party appplication which can scan my script and give me all needed modules ?
You could do something like this to get help in finding commands used and their source/module names. It's very unpolished, just trying to give the idea.
$scriptblock = {
Write-Host "Nothing here"
$files = Get-ChildItem c:\temp
Get-ADUser someuser
Test-NetConnection www.google.com
}
# Uncomment following lines and enter the path to your script file
# $scriptFile = "Path\to\some\scriptfile"
# $scriptblock = [scriptblock]::Create((Get-Content -raw -Path $scriptFile))
$ast = $scriptblock.Ast
$commands = $ast.FindAll( { $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true)
$commandText = foreach ($command in $commands) {
$command.CommandElements[0].Extent.Text
}
$commandText |
Select-Object -Unique |
Sort-Object |
Select-Object #{
Label = "CommandName"
Expression = { $_ }
},
#{
Label = "Source"
Expression = {
(Get-Command $_).Source
}
}
Output
CommandName Source
----------- ------
Get-ADUser ActiveDirectory
Get-ChildItem Microsoft.PowerShell.Management
Test-NetConnection NetTCPIP
Write-Host Microsoft.PowerShell.Utility
Yeah, you could for example test if the module exists on that particular machine by trying to import it as follows
Try {
Import-Module dbaclone -ErrorAction stop
#ErrorAction required as failing to import is not a terminating action
} Catch {
Write-Verbose -Verbose "Failed to find dbaclone module - installing"
Install-Module dbaclone -AllowClobber -Force
Write-Verbose -Verbose "Installed!"
Import-Module dbaclone
}

Powershell: How to find out which running services aren't part of OS and non-MS

Is there a way to find out using Powershell which running services are non-native to Windows ? In other words, services that aren't part Windows OS and are non-Microsoft. I want to find out all the services that were installed by our vendors.
Finding out running services is easy:
Get-Service | Where-Object {$_.Status -eq "Running"}
You can't do with it Get-Service alone, because even though the service-info objects it outputs have a .BinaryPathName property, that property is available in PowerShell Core only and it is not necessarily the true service binary, which is often a DLL hosted by the generic svchost.exe service host.
To find the binary file path in all cases, you must (also) query the service definitions in the registry. Once you have the file path, you can use Get-Item and the .VersionInfo property of the file-info objects returned to extract information such as the product and company name.
The Get-ServiceFileInfo function at the bottom does just that; it allows you to run commands such as:
# Return information about all services that aren't part of Windows.
# (May still include Microsoft services).
Get-ServiceFileInfo | Where ProductName -ne 'Microsoft® Windows® Operating System'
Get-ServiceFileInfo source code (PSv5+, but could be adapted to work with lower versions):
# Note: While it is possible to run without elevated privileges,
# not all file information is retrievable then.
#requires -runAsAdministrator
function Get-ServiceFileInfo {
Set-StrictMode -Version 1
Get-Service | ForEach-Object {
# PowerShell Core only:
# Get the service binary path, which may or may not be the true service
# executable.
$binaryPath = $_.BinaryPathName
# Windows PowerShell:
# We fall back on trying to obtain the "ImagePath" value from the registry.
# Note: Even in PowerShell Core there appear to be services that Get-Service fails
# to query even when running as admin, such as "SshdBroker"
# (a non-terminating error is issued).
# Reading from the registry is needed in that case too,
# but, only succeeds when running as admin.
if (-not $binaryPath) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$($_.Name)" ImagePath } catch { }
}
# Test for svchost.exe, which indicates the need to look for the service-specific DLL path elsewhere.
if ($binaryPath -like '*\svchost.exe *') {
# Get the actual binary (DLL) from the registry, subkey "Parameters", value "ServiceDLL"
# NOTE: Some services exist in *2* (multiple?) incarnations, as "<name>"" and "<name>_<num>"
# Only the "<name>" incarnation has the "ServiceDLL" value, so we fall back on that.
foreach ($keyName in $_.Name, ($_.Name -split '_')[0]) {
# NOTE: Most DLL-based services store the "ServiceDLL" value in the "Parameters" subkey, but
# some have it in the service's root key itself.
foreach ($subKeyName in "$keyName\Parameters", $keyName) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$subKeyName" ServiceDLL } catch { }
if ($binaryPath) { break }
}
}
}
# Sanitize the path:
# * Some values have enclosing "...", so we strip them,
# * others have arguments, so we only take the first token.
$binaryPath = if ($binaryPath.StartsWith('"')) {
($binaryPath -split '"')[1]
} else {
# The path / command line isn't or doesn't start with a double-quoted token, which
# can mean one of two things:
# * It is a command line based on an unquoted executable, possibly with arguments.
# * It is a service DLL path - possibly with spaces in the (expanded) path.
if (Test-Path -LiteralPath $binaryPath -Type Leaf) {
$binaryPath # Value as a whole is a file path
} else {
(-split $binaryPath)[0] # Value is a command line, extract executable
}
}
$FileVersionInfo = if ($binaryPath) { (Get-Item -LiteralPath $binaryPath).VersionInfo }
# Construct the output object.
[pscustomobject] #{
Name = $_.Name
BinaryPath = if ($binaryPath) { $binaryPath } else { '(n/a)'; Write-Error "Failed to determine binary path for service '$($_.Name)'. Try running as admin." }
ProductName = $FileVersionInfo.ProductName
FileDescription = $FileVersionInfo.FileDescription
CompanyName = $FileVersionInfo.CompanyName
}
}
}

fuser equivalent in Powershell?

I want to delete a directory, but a process is using it.
mv : Access to the path 'C:\Users\mike\Documents\myapp\node_modules\' is denied.
Explorer mentions the dir is in use. Is there a Windows Powershell equivalent of fuser?
Note this is a powershell question. I don't want to launch a GUI app.
Try this one:
$lockedFolder="C:\Windows\System32"
Get-Process | %{$processVar = $_;$_.Modules | %{if($_.FileName -like "$lockedFolder*"){$processVar.Name + " PID:" + $processVar.id}}}
This looks for every process that's running in the folder. (or in subdirectories)
With this script, you'll get some more informations:
$lockedFolder="C:\Windows\System32"
Get-Process | %{$processVar = $_;$_.Modules | %{if($_.FileName -like "$lockedFolder*"){$processVar.Name + " PID:" + $processVar.id + " FullName: " + $_.FileName }}}
I think there is no equivalent to fuser, but there is a tool called handle.exe that has to be installed first.
PowerShell script to check an application that's locking a file?
Here's a modified version of #Eldo.Ob's excellent answer that handles relative files.
function fuser($relativeFile){
$file = Resolve-Path $relativeFile
foreach ( $Process in (Get-Process)) {
foreach ( $Module in $Process.Modules) {
if ( $Module.FileName -like "$file*" ) {
$Process | select id, path
}
}
}
}
In use:
> fuser .\node_modules\
Id Path
-- ----
2660 C:\Program Files\nodejs\node.exe

How to find whether a certain file is available in the system path?

My Powershell script must be run from a VS command prompt, and I want to verity that by checking that msbuild.exe is in path. I managed to do it using where.exe:
where.exe msbuild.exe > $null
if ($LASTEXITCODE -ne 0)
{
throw "BuildAll must be run from ""Developer Command Prompt for VS2012"" shortcut."
}
However, this doesn't feel like "the Powershell way" - is there a Powershell-native way to do this?
Try this if the msbuild.exe must be in the same folder as the script is
if ( TEST-PATH (JOIN-PATH (Split-Path -parent $MyInvocation.MyCommand.Definition) "msbuild.exe " ))
{
...
}
else
{
...
}
If you want check if build.exe is available as command (any available path from $env:path) you can do:
if ([bool](Get-Command "msbuild.exe" -ea silentlycontinue))
{
...
}

PowerShell script to check an application that's locking a file?

Using in PowerShell, how can I check if an application is locking a file?
I like to check which process/application is using the file, so that I can close it.
You can do this with the SysInternals tool handle.exe. Try something like this:
PS> $handleOut = handle
PS> foreach ($line in $handleOut) {
if ($line -match '\S+\spid:') {
$exe = $line
}
elseif ($line -match 'C:\\Windows\\Fonts\\segoeui\.ttf') {
"$exe - $line"
}
}
MSASCui.exe pid: 5608 ACME\hillr - 568: File (---) C:\Windows\Fonts\segoeui.ttf
...
This could help you: Use PowerShell to find out which process locks a file. It parses the System.Diagnostics.ProcessModuleCollection Modules property of each process and it looks for the file path of the locked file:
$lockedFile="C:\Windows\System32\wshtcpip.dll"
Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq $lockedFile){$processVar.Name + " PID:" + $processVar.id}}}
You should be able to use the openfiles command from either the regular command line or from PowerShell.
The openfiles built-in tool can be used for file shares or for local files. For local files, you must turn on the tool and restart the machine (again, just for first time use). I believe the command to turn this feature on is:
openfiles /local on
For example (works on Windows Vista x64):
openfiles /query | find "chrome.exe"
That successfully returns file handles associated with Chrome. You can also pass in a file name to see the process currently accessing that file.
You can find a solution using Sysinternal's Handle utility.
I had to modify the code (slightly) to work with PowerShell 2.0:
#/* http://jdhitsolutions.com/blog/powershell/3744/friday-fun-find-file-locking-process-with-powershell/ */
Function Get-LockingProcess {
[cmdletbinding()]
Param(
[Parameter(Position=0, Mandatory=$True,
HelpMessage="What is the path or filename? You can enter a partial name without wildcards")]
[Alias("name")]
[ValidateNotNullorEmpty()]
[string]$Path
)
# Define the path to Handle.exe
# //$Handle = "G:\Sysinternals\handle.exe"
$Handle = "C:\tmp\handle.exe"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\b(\d+)\b)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# //[regex]$matchPattern = "(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+\w+:\s+(?<Path>.*)"
# (?m) for multiline matching.
# It must be . (not \.) for user group.
[regex]$matchPattern = "(?m)^(?<Name>\w+\.\w+)\s+pid:\s+(?<PID>\d+)\s+type:\s+(?<Type>\w+)\s+(?<User>.+)\s+\w+:\s+(?<Path>.*)$"
# skip processing banner
$data = &$handle -u $path -nobanner
# join output for multi-line matching
$data = $data -join "`n"
$MyMatches = $matchPattern.Matches( $data )
# //if ($MyMatches.value) {
if ($MyMatches.count) {
$MyMatches | foreach {
[pscustomobject]#{
FullName = $_.groups["Name"].value
Name = $_.groups["Name"].value.split(".")[0]
ID = $_.groups["PID"].value
Type = $_.groups["Type"].value
User = $_.groups["User"].value.trim()
Path = $_.groups["Path"].value
toString = "pid: $($_.groups["PID"].value), user: $($_.groups["User"].value), image: $($_.groups["Name"].value)"
} #hashtable
} #foreach
} #if data
else {
Write-Warning "No matching handles found"
}
} #end function
Example:
PS C:\tmp> . .\Get-LockingProcess.ps1
PS C:\tmp> Get-LockingProcess C:\tmp\foo.txt
Name Value
---- -----
ID 2140
FullName WINWORD.EXE
toString pid: 2140, user: J17\Administrator, image: WINWORD.EXE
Path C:\tmp\foo.txt
Type File
User J17\Administrator
Name WINWORD
PS C:\tmp>
I was looking for a solution to this as well and hit some hiccups.
Didn't want to use an external app
Open Files requires the local ON attribute which meant systems had to be configured to use it before execution.
After extensive searching I found.
https://github.com/pldmgg/misc-powershell/blob/master/MyFunctions/PowerShellCore_Compatible/Get-FileLockProcess.ps1
Thanks to Paul DiMaggio
This seems to be pure powershell and .net / C#
You can find for your path on handle.exe.
I've used PowerShell but you can do with another command line tool.
With administrative privileges:
handle.exe -a | Select-String "<INSERT_PATH_PART>" -context 0,100
Down the lines and search for "Thread: ...", you should see there the name of the process using your path.
Posted a PowerShell module in PsGallery to discover & kill processes that have open handles to a file or folder.
It exposes functions to: 1) find the locking process, and 2) kill the locking process.
The module automatically downloads handle.exe on first usage.
Find-LockingProcess()
Retrieves process information that has a file handle open to the specified path.
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA
Example: Find-LockingProcess -Path $Env:LOCALAPPDATA | Get-Process
Stop-LockingProcess()
Kills all processes that have a file handle open to the specified path.
Example: Stop-LockingProcess -Path $Home\Documents
PsGallery Link: https://www.powershellgallery.com/packages/LockingProcessKiller
To install run:
Install-Module -Name LockingProcessKiller
I like what the command prompt (CMD) has, and it can be used in PowerShell as well:
tasklist /m <dllName>
Just note that you can't enter the full path of the DLL file. Just the name is good enough.
I've seen a nice solution at Locked file detection that uses only PowerShell and .NET framework classes:
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate,[System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$obj = New-Object Object
$obj | Add-Member Noteproperty FilePath -value $filePath
$obj | Add-Member Noteproperty IsLocked -value $filelocked
$obj
}
If you modify the above function slightly like below it will return True or False
(you will need to execute with full admin rights)
e.g. Usage:
PS> TestFileLock "c:\pagefile.sys"
function TestFileLock {
## Attempts to open a file and trap the resulting error if the file is already open/locked
param ([string]$filePath )
$filelocked = $false
$fileInfo = New-Object System.IO.FileInfo $filePath
trap {
Set-Variable -name Filelocked -value $true -scope 1
continue
}
$fileStream = $fileInfo.Open( [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None )
if ($fileStream) {
$fileStream.Close()
}
$filelocked
}