Pipe stream to other process - powershell

I would like to download a 20GB dump, replace strings and pipe it to mysql.exe while it still downloads in Powershell. But I'm having issues piping the stream.
If my file was already downloaded, I could stream while replacing strings in the file to StdOut with:
Get-Content 'dump.sql' | %{ $_.replace("production_db", "staging_db") }
Or if I also download the file while streaming and replacing strings to StdOut , I could do this:
$url = 'http://MyServer.ext/dump.sql'
& {
$myHttpWebRequest = [System.Net.WebRequest]::Create($url)
$myHttpWebRequest.Headers.Add("Authorization", "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("MyUsername:MyPassword")))
try {
$res = $myHttpWebRequest.GetResponse()
}
catch [System.Net.WebException] {
$res = $_.Exception.Response
}
if ([int] $res.StatusCode -ne 200) {
'Error: ' + [int]$res.StatusCode + " " + $res.StatusCode
} else {
$receiveStream = $res.GetResponseStream()
$encode = [System.Text.Encoding]::GetEncoding("utf-8")
$readStream = [System.IO.StreamReader]::new($receiveStream, $encode)
while (-not $readStream.EndOfStream) {
$readStream.ReadLine().replace("production_db", "staging_db")
}
$res.Close()
$readStream.Close()
}
}
But in both cases, I fail to pipe this as a stream to mysql.exe. It seams the whole stream is first loaded into memory, before being passed on to the mysql.exe process, when I append:
| & 'C:\Program Files\MySQL\MySQL Server 5.7\bin\mysql.exe' -u MyUsername -pMyPassword -h 127.0.0.1
How do I manage to pipe a stream to another process in Powershell?

We know mysql.exe will take standard input because we can run mysql.exe < myfile.sql and it works great. I found this fantastic answer about sending lines of text to the standard input of a process, and it seems to work for me. I'm using the CMD shell to test with since I don't have mysql handy:
# Setup: example commands to feed via stream to process
'echo "hello"
echo "world"
echo "bye"
exit' > 'C:\temp\file.bat'
# create streamreader from file
$readStream = [System.IO.Streamreader]::new('C:\temp\file.bat')
$encode = [System.Text.Encoding]::GetEncoding("utf-8")
For you, just update your replacement and the exe name:
# Create a process for the job
$psi = New-Object System.Diagnostics.ProcessStartInfo;
$psi.FileName = "cmd.exe"; #process file
$psi.UseShellExecute = $false; #start the process from it's own executable file
$psi.RedirectStandardInput = $true; #enable the process to read from standard input
$psi.RedirectStandardOutput = $true #send standard output to object
$p = [System.Diagnostics.Process]::Start($psi);
# Send each line of the file to the process as standard input:
while (-not $readStream.EndOfStream) {
$p.StandardInput.WriteLine(
($readStream.ReadLine().replace("l", "L"))
)
}
$readStream.Close()
# Make sure you don't accidentally close mysql before it's done processing.
# For example, add WriteLine('exit') after the While{}, then use Wait-Process.
# get the output of the process, should also wait for the process to finish
$p.StandardOutput.ReadToEnd()
$p.Close()
In my tests it's working, replacing the commands as they're read, and sending them to the process, and shows the output:
Microsoft Windows [Version 10.0.19043.1110]
(c) Microsoft Corporation. All rights reserved.
C:\>echo "heLLo"
"heLLo"
C:\>echo "worLd"
"worLd"
C:\>echo "bye"
"bye"
C:\>exit
I think you should be able to hook into $p.StandardOutput and read it as you go, but when I tried it would cause the process to hang? Maybe just use mysql logging instead.

Related

handle error from external console application in powershell script

my powershell script calls a third party console application which uses custom commands. I want powershell to try to run that console applications command but if an error is returned (not by the powershell script but the external console app) which contains a specific string then run another command instead. If not just move onto the next instruction in the script.
What would be the best way of handling that, so basically:
if command1 returns "error1" then run command2. if command 1 does not return error1 skip command2 and move down the script.
You can call and catch errors of native applications in many ways.
Some examples:
1. Most easy with no process handling, no distinguishing between success and error.
$nativeAppFilePath = 'ping.exe'
# parameters as collection. Parameter-Value pairs with a space in between must be splitted into two.
$nativeAppParam= #(
'google.com'
'-n'
'5'
)
# using powershell's call operator '&'
$response = & $nativeAppFilePath $nativeAppParam
$response
2. Easy, same as 1., but distinguishing between success and error possible.
$nativeAppFilePath = 'ping.exe'
# parameters as collection. Parameter-Value pairs with a space in between must be splitted into two.
$nativeAppParam= #(
'google2.com'
'-n'
'5'
)
# using powershell's call operator '&' and redirect the error stream to success stream
$nativeCmdResult = & $nativeAppFilePath $nativeAppParam 2>&1
if ($LASTEXITCODE -eq 0) {
# success handling
$nativeCmdResult
} else {
# error handling
# even with redirecting the error stream to the success stream (above)..
# $LASTEXITCODE determines what happend if returned as not "0" (depends on application)
Write-Error -Message "$LASTEXITCODE - $nativeCmdResult"
}
! Now two more complex snippets, which doesn't work with "ping.exe" (but most other applications), because "ping" doesn't raise error events.
3. More complex with process handling, but still process blocking until the application has been finished.
$nativeAppProcessStartInfo = #{
FileName = 'ping.exe' # either OS well-known as short name or full path
Arguments = #(
'google.com'
'-n 5'
)
RedirectStandardOutput = $true # required to catch stdOut stream
RedirectStandardError = $true # required to catch stdErr stream
UseShellExecute = $false # required to redirect streams
CreateNoWindow = $true # does what is says (background work only)
}
try {
$nativeApp= [System.Diagnostics.Process]#{
EnableRaisingEvents = $true
StartInfo = $nativeAppProcessStartInfo
}
[void]$nativeApp.Start()
# Warning: As soon as the buffer gets full, the application could stuck in a deadlock. Then you require async reading
# see: https://stackoverflow.com/a/7608823/13699968
$stdOut = $nativeApp.StandardOutput.ReadToEnd()
$stdErr = $nativeApp.StandardError.ReadToEnd()
# To avoid deadlocks with synchronous read, always read the output stream first and then wait.
# see: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?redirectedfrom=MSDN&view=net-5.0#remarks
$nativeApp.WaitForExit()
if ($stdOut.Result) {
# success handling
$stdOut.Result
}
if ($stdErr.Result) {
# error handling
$stdErr.Result
}
} finally {
$nativeApp.Dispose()
}
4. The most complex with realtime output & reaction, capturing, and so on...
This time with wmic.exe and a nonsense parameter as example.
$appFilePath = 'wmic.exe'
$appArguments = #(
'someNonExistentArgument'
)
$appWorkingDirPath = ''
# handler for events of process
$eventScriptBlock = {
# $Event is an automatic variable. Only existent in scriptblocks used with Register-ObjectEvent
# received app output
$receivedAppData = $Event.SourceEventArgs.Data
# Write output as stream to console in real-time (without -stream parameter output will produce blank lines!)
# (without "Out-String" output with multiple lines at once would be displayed as tab delimited line!)
Write-Host ($receivedAppData | Out-String -Stream)
<#
Insert additional real-time processing steps here.
Since it is in a different scope, variables changed in this scope will not get changed in parent scope
and scope "$script:" will not work as well. (scope "$global:" would work but should be avoided!)
Modify/Enhance variables "*MessageData" (see below) before registering the event to modify such variables.
#>
# add received data to stringbuilder definded in $stdOutEventMessageData and $stdErrEventMessageData
$Event.MessageData.Data.AppendLine($receivedAppData)
}
# MessageData parameters for events of success stream
$stdOutEventMessageData = #{
# useful for further usage after application has been exited
Data = [System.Text.StringBuilder]::new()
# add additional properties necessary in event handler scriptblock above
}
# MessageData parameters for events of error stream
$stdErrEventMessageData = #{
# useful for further usage after application has been exited
Data = [System.Text.StringBuilder]::new()
# add additional properties necessary in event handler scriptblock above
}
#######################################################
#region Process-Definition, -Start and Event-Subscriptions
#------------------------------------------------------
try {
$appProcessStartInfo = #{
FileName = $appFilePath
Arguments = $appArguments
WorkingDirectory = $appWorkingDirPath
RedirectStandardOutput = $true # required to catch stdOut stream
RedirectStandardError = $true # required to catch stdErr stream
# RedirectStandardInput = $true # only useful in some circumstances. Didn't find any use yet, but mentioned in: https://stackoverflow.com/questions/8808663/get-live-output-from-process
UseShellExecute = $false # required to redirect streams
CreateNoWindow = $true # does what is says (background work only)
}
$appProcess = [System.Diagnostics.Process]#{
EnableRaisingEvents = $true
StartInfo = $appProcessStartInfo
}
# to obtain available events of an object / type, read the event members of it: "Get-Member -InputObject $appProcess -MemberType Event"
$stdOutEvent = Register-ObjectEvent -InputObject $appProcess -Action $eventScriptBlock -EventName 'OutputDataReceived' -MessageData $stdOutEventMessageData
$stdErrEvent = Register-ObjectEvent -InputObject $appProcess -Action $eventScriptBlock -EventName 'ErrorDataReceived' -MessageData $stdErrEventMessageData
[void]$appProcess.Start()
# async reading
$appProcess.BeginOutputReadLine()
$appProcess.BeginErrorReadLine()
while (!$appProcess.HasExited) {
# Don't use method "WaitForExit()"! This will not show the output in real-time as it blocks the output stream!
# using "Sleep" from System.Threading.Thread for short sleep times below 1/1.5 seconds is better than
# "Start-Sleep" in terms of PS overhead/performance (Test it yourself)
[System.Threading.Thread]::Sleep(250)
# maybe timeout ...
}
} finally {
if (!$appProcess.HasExited) {
$appProcess.Kill() # WARNING: Entire process gets killed!
}
$appProcess.Dispose()
if ($stdOutEvent -is [System.Management.Automation.PSEventJob]) {
Unregister-Event -SourceIdentifier $stdOutEvent.Name
}
if ($stdErrEvent -is [System.Management.Automation.PSEventJob]) {
Unregister-Event -SourceIdentifier $stdErrEvent.Name
}
}
#------------------------------------------------------
#endregion
#######################################################
$stdOutText = $stdOutEventMessageData.Data.ToString() # final output for further usage
$stdErrText = $stdErrEventMessageData.Data.ToString() # final errors for further usage

query on powershell script that downloads the Microsoft Ebook Giveaway books

Got a whatsapp message that Microsoft is giving away free ebooks from the below url.
URL : Microsoft Ebook Giveaway
To download all the books in one go, the following powershell script was used, which is available in the same url.
Now my problem is, if I run the powershell script as a whole, it is not throwing any error. All the books from the url gets downloaded to a single location in my computer.
But if I try to run the script line by line to understand what each statement does, it is giving the following error when the , $bookList = Invoke-WebRequest $downLoadList gets executed,
Now to resolve this error, there are many others posts in stack overflow, that passes the username and password to overcome this error. Those scripts / solutions are not working at my end.
More than the error, why is it, that the script runs without any errors/issues when I execute the full script, but throws an error when I execute line by line ?
Any inputs on the nature of execution or helpful tips in overcoming the error will be useful... Thank you.
Error :
Invoke-WebRequest : (my ip number )
Credentials are missing.
Make sure to specify a domain with your username
This website has been blocked by a cyber security policy
and SecureWeb does not currently support web exceptions
If you have an exception, copy the link below into a new tab
http://ligman.me/2tk1D2V
At line:1 char:13
+ $bookList = Invoke-WebRequest $downLoadList
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest)
[Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.
InvokeWebRequestCommand
PowerShell Script :
###############################################################
# Eric Ligmans Amazing Free Microsoft eBook Giveaway
# https://blogs.msdn.microsoft.com/mssmallbiz/2017/07/11/largest-free-microsoft-ebook-giveaway-im-giving-away-millions-of-free-microsoft-ebooks-again-including-windows-10-office-365-office-2016-power-bi-azure-windows-8-1-office-2013-sharepo/
# Link to download list of eBooks
# http://ligman.me/2tk1D2V
# Thanks David Crosby for the template (https://social.technet.microsoft.com/profile/david%20crosby/)
#
# Modified by Robert Cain (http://arcanecode.me)
# Added code to check to see if a book was already downloaded,
# and if so was it the correct file size. If so, the book
# download is skipped. This allows users to simply rerun the
# script if their download process is interrupted.
###############################################################
# Set the folder where you want to save the books to
$dest = "I:\new_microsoft\" # Make sure the file path ends in a \
# Download the source list of books
$downLoadList = "http://ligman.me/2tk1D2V"
$bookList = Invoke-WebRequest $downLoadList
# Convert the list to an array
[string[]]$books = ""
$books = $bookList.Content.Split("`n")
# Remove the first line - it's not a book
$books = $books[1..($books.Length -1)]
$books # Here's the list
# Get the total number of books we need to download
$bookCount = $($books).Count
# Set a simple counter to let the user know what book
# number we're currently downloading
$currentBook = 0
# As an option, we can have it log progress to a file
$log = $true
if ($log -eq $true)
{
# Construct a log file name based on the date that
# we can save progress to
$dlStart = Get-Date
$dlStartDate = "$($dlStart.Year)-$($dlStart.Month)-$($dlStart.Day)"
$dlStartTime = "$($dlStart.Hour)-$($dlStart.Minute)-$($dlStart.Second)"
$logFile = "$($dest)BookDlLog-$dlStartDate-$dlStartTime.txt"
}
# Download the books
foreach ($book in $books)
{
# Increment current book number
$currentBook++
try
{
# Grab the header with the books full info
$hdr = Invoke-WebRequest $book -Method Head
# Get the title of the book from the header then
# make it a safe string (remove special characters)
$title = $hdr.BaseResponse.ResponseUri.Segments[-1]
$title = [uri]::UnescapeDataString($title)
# Construct the path to save the file to
$saveTo = $dest + $title
# If the file doesn't exist, download it
if ($(Test-Path $saveTo) -eq $false)
{
$msg = "Downloading book $currentBook of $bookCount - $title"
$msg
if ($log -eq $true) { "`n$($msg)" | Add-Content $logFile }
Invoke-WebRequest $book -OutFile $saveTo
}
else
{
# If it does exist, we need to make sure it wasn't
# a partial download. If the file size on the server
# and the file size on local disk don't match,
# redownload it
# Get the size of the file from the download site
$dlSize = $hdr.BaseResponse.ContentLength
# Get the size of the file on disk
$fileSize = $(Get-ChildItem $saveTo).Length
if ($dlSize -ne $fileSize)
{
# If not equal we need to download the book again
$msg = "Redownloading book $currentBook of $bookCount - $title"
$msg
if ($log -eq $true) { "`n$($msg)" | Add-Content $logFile }
Invoke-WebRequest $book -OutFile $saveTo
}
else
{
# Otherwise we have a good copy of the book, just
# let the user know we're skipping it.
$msg = "Book $currentBook of $bookCount ($title) already exists, skipping it"
$msg
if ($log -eq $true) { "`n$($msg)" | Add-Content $logFile }
}
}
} # end try
catch
{
$msg = "There was an error downloading $title. You may wish to try to download this book manually."
Write-Host $msg -ForegroundColor Red
if ($log -eq $true) { "`n$($msg)" | Add-Content $logFile }
} # end catch
} # end foreach
# Let user know we're done, and give a happy little beep
# in case they aren't looking at the screen.
#"Done downloading all books"
#[Console]::Beep(500,300)

How do I capture StdOut from External programs from a PowerShell script?

I write VBScript script and often times I need to run a command line program and capture the the information that was written to std out so that output can be scraped. All of this is hidden from the person that executes the VBScript.
Here is an quick and stupid example:
cmd = "ping google.com"
Set objWSH = CreateObject( "WScript.Shell" )
Set Ret = objWSH.exec(cmd)
StdOut = Ret.StdOut.ReadAll()
myarray = Split(StdOut,vbcrlf)
For each line in myarray
If Instr(line,"Average") then avg = Right(line,Len(line) - InStrRev(line," "))
Next
wscript.echo "Google.com = " & avg
My question is not "how do I ping" as I'm showing in my example code.
I need to know how to run command-line programs from within a PowerShell script so that they are not seen but in a way that I can grab the output written to std-out.
Store the result in a variable.
$result = Invoke-Expression 'ping www.google.com'
or
$result = iex 'ping www.google.com'
To be close to your vbscript but with a RegEx:
foreach ($line in (ping.exe google.com)){
if ($line -match 'Average = (\d+)ms'){
"Google.com = $($matches[1])"
}
}
Sample output:
Google.com = 12
A more PowerShell way:
$Avg = (test-connection google.com).responsetime|measure -average|select -expandproperty Average
"Google.com = $Avg"
sample output:
Google.com = 25.25

How to capture console app output in Octopus custom PowerShell script?

I simply created an console app with argument number check at the very beginning. And after the package is deployed, in the deployment PowerShell script part, I directly call this app with no argument to test the script. It seems Octopus just captures the exit code and show there is no output from the app captured in task log at all.
static void Main(string[] args)
{
if (args.Length < 4)
{
Console.WriteLine("Invalid argument number");
Environment.ExitCode = -1;
return;
}
}
However, if I simply put "echo 'test'" or even just 'test' string in the script, the output was captured in Octopus deployment task log. Any idea what is the correct way to log console app in the script? Thanks.
Sorry, it is not fault of Octopus, It is actually the console app was built for .Net framework 4.6.1 but the tentacle server is only having 4.5.2. When I run the app on that server through remote desktop, it pops up error message saying 4.6.1 .Net framework is missing. Rebuild the app with 4.5.2 fixed this issue. However it was really hard to find this out because Octopus task log has nothing related to that. Hope this would help someone else in the future.
Create a file named "yourpowershellfile.ps1" or Create e deployment step "Run a script".
Try this powershell with OctopusDeploy,
$FullPath = "C:\MyFolder"
if ($OctopusEnvironmentName -ceq 'Development')
{
Write-Host "Console app will be execute"
& "$FullPath\yourconsolefile.exe" | Write-Host
Write-Host "Console app execution has finied"
}
You should try "Write-Output" instead of "Write-Host" Review the Octopus deploy task log.
If you found this question because you really need to get the console output following executables run in your Octopus deployment steps you can use the following code. It took a bit of research to write the following function I happily re-use accross Octopus steps where I need the console output:
Function Invoke-CmdCommand{
param(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({(Test-Path $_.Trim('"').Trim(''''))})]
[string]$Executable,
[string]$Parameters = '',
[switch]$CmdEscape
)
BEGIN{
Write-Verbose "Start '$($MyInvocation.Mycommand.Name)'"
$nl = [Environment]::NewLine
$exitCode = 0
$cmdOutput = [string]::Empty
# next line wrap string in quotes if there is a space in the path
#$Executable = (Format-WithDoubleQuotes $Executable -Verbose:$([bool]($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent)))
$command = "{0} {1}" -f $Executable, $Parameters
Write-Verbose "COMMAND: $command"
$terminatePrompt = "/C" # https://ss64.com/nt/cmd.html
$comSpec = $env:ComSpec
if($CmdEscape.IsPresent){
$command = "`"$command`""
Write-Verbose "ESCAPED COMMAND: $command"
}
}
PROCESS{
$cmdResult = .{
# script block exec: dot does not create local scope as opposed to ampersand
.$comSpec $terminatePrompt $command '2>&1' | Out-String | Tee-Object -Variable cmdOutput
return $LastExitCode
}
$exitCode = $cmdResult[$cmdResult.length-1]
if($exitCode -ne 0){
Write-Host "FAILED with ExitCode: $exitCode; ERROR executing the command:$nl$command$nl" -ForegroundColor Red
Write-Host "ERROR executing the command:$nl$command" -ForegroundColor Yellow
}else{
Write-Host "SUCCESSFULLY executed the command:$nl$command$nl"
}
}
END{
if($Host.Version.Major -le 3){
return ,$cmdOutput # -NoEnumerate equivalent syntax since it is not available in version 2.0
}else{
Write-Output -NoEnumerate $cmdOutput
}
Write-Verbose "End '$($MyInvocation.Mycommand.Name)'"
}
}
USAGE:
Invoke-CmdCommand -Executable (Join-Path (Split-Path $env:ComSpec) ping.exe) -Parameters 'localhost'
OUTPUT:
Pinging localhost [::1] with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
We just add a Deploy.ps1 to the package, with the following code:
& .\MyCompany.Foo.Bar.exe 2>&1
Resources:
In the shell, what does " 2>&1 " mean?

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
}