I would like to have a screenshot tool in PS. Because I don't want to reinvent the wheel I searched and found a script at github (https://github.com/mikepruett3/psfetch), which I adapted for my needs.
Now I would like to change the behaviour - when the script is started with no parameter it should make a screenshot in the current directory. If the user enters a path (with -Path) the screenshot should be saved there.
My idea was to define (in my case) $Tarpath and redefine it when the option is given. How to do this?
Here is my actual script:
# PSFetch.ps1
# A Screenfetch writen in PowerShell
#
# -----------------------------------------------------------
# The Original Inspirations for CMDfetch:
# -----------------------------------------------------------
# screenFetch by KittyKatt
# https://github.com/KittyKatt/screenFetch
# A very nice screenshotting and information tool. For GNU/Linux (Almost all Major Distros Supported) *This has been ported to Windows, link below.*
#
# archey by djmelik
# https://github.com/djmelik/archey
# Another nice screenshotting and information tool. More hardware oriented than screenFetch. For GNU/Linux
# -----------------------------------------------------------
#
# DONE: Function to Take the Screenshot
Function Take-Screenshot {
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = "$PSScriptRoot"
)
PROCESS {
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null
# Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
$bounds = [Windows.Forms.SystemInformation]::VirtualScreen
# Check Path for Trailing BackSlashes
# $TarPath = $PSScriptRoot
if ( $TarPath.EndsWith("\") ) {
$TarPath = $TarPath.Substring(0,$Path.Length-1)
}
# Define The Target Path
$stamp = get-date -f MM-dd-yyyy_HH_mm_ss
$target = "$TarPath\screenshot-$stamp.png"
# Take the Screenshot
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
$bmp.Save($target)
$graphics.Dispose()
$bmp.Dispose()
}
}
# DONE: Fix support for Multiple Monitors
# FROM: Shay Levy's Response - http://stackoverflow.com/questions/7967699/get-screen-resolution-using-wmi-powershell-in-windows-7
$ScreenWidth = 0
$ScreenHeight = 0
Add-Type -AssemblyName System.Windows.Forms
$DisplayCount = [System.Windows.Forms.Screen]::AllScreens.Bounds.Count
$Bounds = [System.Windows.Forms.Screen]::AllScreens | Select-Object -ExpandProperty Bounds
$ScreenWidth = $Bounds | Measure-Object -Property Width -Sum | Select-Object -ExpandProperty Sum
$ScreenHeight = $Bounds | Measure-Object -Property Height -Maximum | Select-Object -ExpandProperty Maximum
$RESOLUTION = "$ScreenWidth x $ScreenHeight"
# Take Screenshot if the Parameters are assigned...
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath $target
edit
i forgot to remove the $tarpath int the PROCESS-block.
It remained here from my first tests...
OK, I solved it myself.
First of all [string]$TarPath = "$PSScriptRoot" doesn't work at all!
The variable is always empty.
However, my first idea was to define $TarPath and leave it unchanged until it defined again. This turned out that doesn't work.
Here is my solution:
# Define The Target Path
Write-Host "Please enter Screenshot-Path"
$TarPath = Read-Host "Else the screenshot will be in $PWD"
if (!$TarPath) {$TarPath = $pwd}
If nothing is entered at the prompt $pwd will be used.
Well, yes, that's about it, that approach will work. The only thing is that in the PROCESS block, you re-assign once again your $TarPath, making your fallback mechanism ineffective:
$TarPath = $PSScriptRoot
Delete that line and it will work like a charm.
Additionally, you could add validations such as making sure the parameter can be null, but not empty, and must be a valid path:
[ValidateScript({if ($_){ Test-Path $_}})]
[string]$TarPath = "$PSScriptRoot"
One last thing, if you want, as described in your question, to let the user use -Path on the call, you can also add an alias to your Param.
[Alias('Path')]
[ValidateScript({if ($_){ Test-Path $_}})]
[string]$Path = "$PSScriptRoot"
You redifine $TarPath in your function body:
$TarPath = $PSScriptRoot
This unconditionally supersedes any value previously assigned to the parameter. Remove the line and you can pass the parameter like this:
Take-Screenshot -TarPath 'C:\some\folder'
or omit the parameter to leave it at its default value ($PSScriptRoot).
I'd recommend to also change the line
$target = "$TarPath\screenshot-$stamp.png"
into this:
$target = Join-Path $TarPath "screenshot-$stamp.png"
so you don't need to fiddle around with trailing backslashes.
Function Take-Screenshot {
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = "$PSScriptRoot"
)
PROCESS {
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null
# Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
$bounds = [Windows.Forms.SystemInformation]::VirtualScreen
# Define The Target Path
$stamp = get-date -f MM-dd-yyyy_HH_mm_ss
$target = Join-Path $TarPath "screenshot-$stamp.png"
# Take the Screenshot
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
$bmp.Save($target)
$graphics.Dispose()
$bmp.Dispose()
}
}
Addendum: There are two scenarios where defining the default value for the parameter -TarPath as $TarPath = "$PSScriptRoot" doesn't work:
The parameter is defined as a parameter to the script (not to a function within the script) and the script is run from CMD:
powershell -File 'C:\path\to\script.ps1'
The script is run with PowerShell v2. The variable was only available in modules prior to PowerShell v3.
In both scenarios "$PScriptRoot" can be replaced with $PWD.Path:
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = $PWD.Path
)
You just need to modify the last line of the script with either of the following:
For default directory:
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight
For custom directory:
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath "D:\Piyush\temp"
And comment out the following line in the PROCESS block as you don't want to override the custom directory path with the default one.
$TarPath = $PSScriptRoot
Related
I'm trying to convert a few thousand home videos to a smaller format. However, encoding the video changed the created and modified timestamp to today's date. I wrote a powershell script that successfully (somehow) worked by writing the original file's modified timestamp to the new file.
However, I couldn't find a way in powershell to modify the "Media created" timestamp in the file's details properties. Is there a way to add a routine that would either copy all of the metadata from the original file, or at least set the "media created" field to the modified date?
When I searched for file attributes, it looks like the only options are archive, hidden, etc. Attached is the powershell script that I made (please don't laugh too hard, haha). Thank you
$filepath1 = 'E:\ConvertedMedia\Ingest\' # directory with incorrect modified & create date
$filepath2 = "F:\Backup Photos 2020 and DATA\Data\Photos\Photos 2021\2021 Part1\Panasonic 3-2-21\A016\PRIVATE\PANA_GRP\001RAQAM\" # directory with correct date and same file name (except extension)
$destinationCodec = "*.mp4" # Keep * in front of extension
$sourceCodec = ".mov"
Get-ChildItem $filepath1 -File $destinationCodec | Foreach-Object { # change *.mp4 to the extension of the newly encoded files with the wrong date
$fileName = $_.Name # sets fileName variable (with extension)
$fileName # Optional used during testing- sends the file name to the console
$fileNameB = $_.BaseName # sets fileNameB variable to the filename without extension
$filename2 = "$filepath2" + "$fileNameB" + "$sourceCodec" # assembles filepath for source
$correctTime = (Get-Item $filename2).lastwritetime # used for testing - just shows the correct time in the output, can comment out
$correctTime # prints the correct time
$_.lastwritetime = (Get-Item $filename2).lastwritetime # modifies lastwritetime of filepath1 to match filepath2
$_.creationTime = (Get-Item $filename2).lastwritetime # modifies creation times to match lastwritetime (comment out if you need creation time to be the same)
}
Update:
I think I need to use Shell.Application, but I'm getting an error message "duplicate keys ' ' are not allowed in hash literals" and am not sure how to incorporate it into the original script.
I only need the "date modified" attribute to be the same as "lastwritetime." The other fields were added just for testing. I appreciate your help!
$tags = "people; snow; weather"
$cameraModel = "AG-CX10"
$cameraMaker = "Panasonic"
$mediaCreated = "2/16/1999 5:01 PM"
$com = (New-Object -ComObject Shell.Application).NameSpace('C:\Users\philip\Videos') #Not sure how to specify file type
$com.Items() | ForEach-Object {
New-Object -TypeName PSCustomObject -Property #{
Name = $com.GetDetailsOf($_,0) # lists current extended properties
Tags = $com.GetDetailsOf($_,18)
CameraModel = $com.GetDetailsOf($_,30)
CameraMaker = $com.GetDetailsOf($_,32)
MediaCreated = $com.GetDetailsOf($_,208)
$com.GetDetailsOf($_,18) = $tags # sets extended properties
$com.GetDetailsOf($_,30) = $cameraModel
$com.GetDetailsOf($_,32) = $cameraMaker
$com.GetDetailsOf($_,32) = $mediaCreated
}
}
Script Example
File Properties Window
I think your best option is to drive an external tool/library from Powershell rather than using the shell (not sure you can actually set values this way tbh).
Its definitely possible to use FFMpeg to set the Media Created metadata of a file like this:
ffmpeg -i input.MOV -metadata creation_time=2000-01-01T00:00:00.0000000+00:00 -codec copy output.MOV
This would copy input.MOV file to new file output.MOV and set the Media Created metadata on the new output.MOV. This is very inefficient - but it does work.
You can script ffmpeg something like the below. The script will currently output the FFMpeg commands to the screen, the commented out Start-Process line can be used to execute ffmpeg.
gci | where Extension -eq ".mov" | foreach {
$InputFilename = $_.FullName;
$OutputFilename = "$($InputFilename)-fixed.mov";
Write-Host "Reading $($_.Name). Created: $($_.CreationTime). Modifed: $($_.LastWriteTime)";
$timestamp = Get-Date -Date $_.CreationTime -Format O
Write-Host "ffmpeg -i $InputFilename -metadata creation_time=$timestamp -codec copy $OutputFilename"
# Start-Process -Wait -FilePath C:\ffmpeg\bin\ffmpeg.exe -ArgumentList #("-i $InputFilename -metadata creation_time=$timestamp -codec copy $($OutputFilename)")
}
I have slightly modified this PowerShell script for my work with DOCX files but would like to edit the DOCX file in place.
After running the script, the terminal displays the error message: "You cannot call a method on a null-value expression" in reference to line 43, $document.Save().
I am not sure the reason for the error as the path to $document is already defined earlier in the script. What am I missing?
Here is the whole script:
Param ([string]$path = $(throw "-path is required."))
Import-Module "C:\scripts\PSGenericMethods.psm1"
[System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Open XML SDK\V2.5\lib\DocumentFormat.OpenXml.dll") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Packaging") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Wordprocessing") | out-null
[Reflection.Assembly]::LoadWithPartialName("OpenXmlPowerTools") | out-null
[DocumentFormat.OpenXml.Packaging.WordprocessingDocument]$document = $null
$document = [DocumentFormat.OpenXml.Packaging.WordprocessingDocument]::Open($path, $true)
[DocumentFormat.OpenXml.Packaging.MainDocumentPart]$MainDocumentPart = $document.MainDocumentPart
[DocumentFormat.OpenXml.Wordprocessing.Document]$InnerDocument = $document.Document
[DocumentFormat.OpenXml.Wordprocessing.Body]$Body = $document.Body
[DocumentFormat.OpenXml.Wordprocessing.Paragraph]$paragraph = $document.Paragraph
[DocumentFormat.OpenXml.Wordprocessing.ParagraphMarkRunProperties]$ParagraphMarkRunProperties = $document.ParagraphMarkRunProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties]$ParagraphProperties = $document.ParagraphProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId]$ParagraphStyleId = $document.ParagraphStyleId
[DocumentFormat.OpenXml.Wordprocessing.Run]$run = $document.Run
$paragraphs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Paragraph
$runs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Run
foreach ($run in $runs) {
if ($run.RunProperties.Languages.Val) {
<#[String]$value = $run.InnerText#>
[String]$language = $run.RunProperties.Languages.Val
'{{$span xml:lang="{0}"$}}{1}{{$/span}}$' -f $language, $run.InnerText
}
}
$document.close()
Update:
After modifying the script, I now no longer have the problem with "You cannot call a method on a null value expression" error. The problem is that I am not sure how to save results to the file.
This may be a duplicate of this thread.
Can anyone suggest a method for saving the changes to the file?
Variable names in PowerShell are NOT case-sensitive. You destroy your "document" with this line I think:
[DocumentFormat.OpenXml.Wordprocessing.Document]$Document = $document.Document
Because $Document is the same variable as $document.
I'm trying to create a Powershell script that will setup a brand new workspace in a temporary location, do a GetLatest on selected solutions/projects, and download the source code so that I can then trigger further build/versioning operations.
I think I have the script more or less right, but the problem is every time I run this, it tells me there were 0 operations... i.e. I already have the latest versions. This results in nothing at all being downloaded.
Can anyone see what I'm doing wrong?
$subfolder = [System.Guid]::NewGuid().ToString()
$tfsServer = "http://tfsserver:8080/tfs"
$projectsAndWorkspaces = #(
#("$/Client1/Project1","D:\Builds\$subfolder\Client1\Project1"),
#("$/Client1/Project2","D:\Builds\$subfolder\Client1\Project2"),
)
$tfsCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsServer)
$tfsVersionCtrl = $tfsCollection.GetService([type] "Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer")
$tfsWorkspace = $tfsVersionCtrl.CreateWorkspace($subfolder, $tfsVersionCtrl.AuthorizedUser)
Write-Host "Operations:"
foreach ($projectAndWs in $projectsAndWorkspaces)
{
if (-not(Test-Path $projectAndWs[1]))
{
New-Item -ItemType Directory -Force -Path $projectAndWs[1] | Out-Null
}
$tfsWorkspace.Map($projectAndWs[0], $projectAndWs[1])
$recursion = [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full
$itemSpecFullTeamProj = New-Object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec($projectAndWs[0], $recursion)
$fileRequest = New-Object Microsoft.TeamFoundation.VersionControl.Client.GetRequest($itemSpecFullTeamProj, [Microsoft.TeamFoundation.VersionControl.Client.VersionSpec]::Latest)
$getStatus = $tfsWorkspace.Get($fileRequest, [Microsoft.TeamFoundation.VersionControl.Client.GetOptions]::Overwrite)
Write-Host ("[{0}] {1}" -f $getStatus.NumOperations, ($projectAndWs[0].Substring($projectAndWs[0].LastIndexOf("/") + 1)))
}
Write-Host "Finished"
The $tfsServer = "http://tfsserver:8080/tfs" should be $tfsServer = "http://tfsserver:8080/tfs/nameOfACollection"
The "$/Client1/Project1" string smells. I would add a backtick before the dollar sign so it is not read as a variable or use single quotes.
Backtick
"`$/Client1/Project1"
Single quote
'$/Client1/Project1'
I can pin some programs to taskbar on Win7 using PowerShell.
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}
How do I modify the above code to pin a program to the Start menu?
Another way
$sa = new-object -c shell.application
$pn = $sa.namespace($env:windir).parsename('notepad.exe')
$pn.invokeverb('startpin')
Or unpin
$pn.invokeverb('startunpin')
Use the code below
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Start Men&u'}
if ($verb) {$verb.DoIt()}
Note: the change is in the fourth line.
The main problem with most of the solution is that they enumerate the verbs on a file, search for the string to perform the action (“Pin to Startmenu” etc.) and then execute it. This does not work if you need to support 30+ languages in your company, except you use external function to search for the localized command (see answer from shtako-verflow).
The answer from Steven Penny is the first that is language neutral and does not need any external code. It uses the verbs stored in the registry HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449} and HKEY_CLASSES_ROOT\CLSID\{a2a9545d-a0c2-42b4-9708-a0b2badd77c8}
Based on this, here’s the code we are now using:
function PinToTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarpin"
}
function UnpinFromTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarunpin"
}
function PinToStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startpin"
}
function UnpinFromStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startunpin"
}
function ExecuteVerb {
param(
[Parameter(Mandatory=$true)][string]$File,
[Parameter(Mandatory=$true)][string]$Verb
)
$path = [System.Environment]::ExpandEnvironmentVariables($File)
$basePath = split-path $path -parent #retrieve only the path File=C:\Windows\notepad.exe -> C:\Windows
$targetFile = split-path $path -leaf #retrieve only the file File=C:\Windows\notepad.exe -> notepad.exe
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace($basePath)
if ($folder)
{
$item = $folder.Parsename($targetFile)
if ($item)
{
$item.invokeverb($Verb)
# "This method does not return a value." (http://msdn.microsoft.com/en-us/library/windows/desktop/bb787816%28v=vs.85%29.aspx)
# Therefore we have no chance to know if this was successful...
write-host "Method [$Verb] executed for [$path]"
}
else
{
write-host "Target file [$targetFile] not found, aborting"
}
}
else
{
write-host "Folder [$basePath] not found, aborting"
}
}
#PinToTaskbar "%WINDIR%\notepad.exe"
#UnpinFromTaskbar "%WINDIR%\notepad.exe"
PinToStartmenu "%WINDIR%\notepad.exe"
#UnpinFromStartmenu "%WINDIR%\notepad.exe"
See the script (international) here : http://gallery.technet.microsoft.com/scriptcenter/b66434f1-4b3f-4a94-8dc3-e406eb30b750
If you want to add an action like Pin to Modern UI interface (Windows 8), at $verbs, add 51201
Steven Penny's second answer above worked well for me. Here are a couple more tidbits.
It's doing COM through PowerShell, so you can do the same thing with pretty much any COM client. For example, here's an AutoHotkey version.
Shell := ComObjCreate("Shell.Application")
Target := Shell.Namespace(EnvGet("WinDir")).ParseName("Notepad.exe")
Target.InvokeVerb("startpin")
VBScript or InnoSetup would look almost the same except for the function used to create the object.
I also found that I have one program that pinned OK, but didn't have the right icon and/or description because of limitations in the compiler. I just made a little 1-line WinForms app that starts the target with Process.Start, and then added the appropriate icon, and the name I wanted in the Start Menu in the Title property in AppInfo.cs.
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
}