Powershell – Who locked my folder?

Lets face it, we all have been in that weird situation, where we want to delete a folder and we get a prompt saying – “The action can’t be completed because the file is open in another program”. If we are aware of such a program (process), that is using our file, then we can just go and kill that process.

But what if we have 100s of files in that folder and multiple processes running? How do we pin point the culprit process that is using the random file, under the folder that we are trying to delete.

I know that there are a few third-party software’s that are available to solve this issue. Since we are Powershell Lovers, lets solve this problem the Powershell way.

In my demonstration, I have a text document called “demo.txt” under the folder, “E:\Work\Powershell\scripts\demo”. Now, I open that file using a notepad. When I try to delete the folder “demo”, I will get the below warning.

cant delete

To solve this problem, we can query the Win32_Process WMI class to find out the culprit process that is using our file.

Run the below cmdlet:

$location = 'E:\Work\Powershell\scripts\demo'
Get-WmiObject Win32_Process | where {$_.commandLine -like "*$location*"} | select name


From the screen shot, you can see that we accurately spotted that the “notepad.exe” process was using our file. We can now terminate the process and then proceed in deleting the file.

This cmdlet can be used as a template or as a starting phase to develop more complicated logic/script accordingly.



Powershell – Writing a parameterized function

As a system administrator or an ITPro we need to run cmdlets to administer our environment. Using powershell, administrating gets pretty easy. But if you have to run same cmdlets again and again for multiple boxes, the process may be tiring or irritating. We need a way  to tell the powershell to do tasks repeatedly, without our intervention (unless it is necessary), and a way to achieve modularity among the tasks that we perform. This can be achieved using functions.

We will be talking about three different types of functions in a three part series. In this blog we will discuss about the simplest form of a function called – Parameterized function.

Lets look at the two cmdlets below:

Get-WmiObject Win32_OperatingSystem -ComputerName vaio | select @{l='ComputerName';e={$_.__SERVER}}, BuildNumber, ServicePackMajorVersion
Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ComputerName vaio | select @{l='SysDriveFree(MB)';e={$_.Freespace/1MB -as [int]}}

We can run them in the shell. The problem is that these two commands will return two independent sets of results. Another problem is that the computer name is hard coded.

To achieve modulatity, we can break down the task into 3 steps:

  • We need to get a computer name or several computer names from someplace.
  • We have to retrieve the Win32_OperatingSystem WMI Class.
  • We have to retrieve the Win32_LogicalDisk WMI Class.

Trying to get all the output into a single table

$computername = 'vaio'

$os = Get-WmiObject Win32_OperatingSystem -ComputerName vaio | select @{l='ComputerName';e={$_.__SERVER}}, BuildNumber, ServicePackMajorVersion

$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ComputerName vaio | select @{l='SysDriveFree(MB)';e={$_.Freespace/1MB -as [int]}}

Write-Host "Computer`tBuildNumber`tSPVersion`tFreeSpace"
Write-Host "========`t==========`t==========`t========="
Write-Host ("{0}`t{1}`t{2}`t{3}" -f ($os.ComputerName),($os.BuildNumber),($os.ServicePackMajorVersion),($disk.'SysDriveFree(MB)'))

The output format is not that great, but the information is what we need.

What we did in the above code was:

  • We have kept the computer name in a variable.
  • We are using Write-Host to create a table header.
  • We are using -f to format the table row.

Wrapping the code in a function

We want to modularize our code into function, which is a self-contained, stand alone unit of work that we can distribute easily. An easy way make a function is to wrap it in a function declaration, as shown here:

function Get-ServerInfo {

$computername = 'vaio'

$os = Get-WmiObject Win32_OperatingSystem -ComputerName vaio | select @{l='ComputerName';e={$_.__SERVER}}, BuildNumber, ServicePackMajorVersion

$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ComputerName vaio | select @{l='SysDriveFree(MB)';e={$_.Freespace/1MB -as [int]}}

Write-Host "Computer`tBuildNumber`tSPVersion`tFreeSpace"
 Write-Host "========`t==========`t==========`t========="
 Write-Host ("{0}`t{1}`t{2}`t{3}" -f ($os.ComputerName),($os.BuildNumber),($os.ServicePackMajorVersion),($disk.'SysDriveFree(MB)'))


Parameterizing the function

We need to change something in the function, as we do not want the computer name to always be ‘vaio’. The solution is to change the $computername from a variable into a parameter, which we can do by adding it to a Param() block, at the top of the function.

function Get-ServerInfo {

param {
 $computername = 'localhost'

$os = Get-WmiObject Win32_OperatingSystem -ComputerName vaio | select @{l='ComputerName';e={$_.__SERVER}}, BuildNumber, ServicePackMajorVersion

$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ComputerName vaio | select @{l='SysDriveFree(MB)';e={$_.Freespace/1MB -as [int]}}

Write-Host "Computer`tBuildNumber`tSPVersion`tFreeSpace"
 Write-Host "========`t==========`t==========`t========="
 Write-Host ("{0}`t{1}`t{2}`t{3}" -f ($os.ComputerName),($os.BuildNumber),($os.ServicePackMajorVersion),($disk.'SysDriveFree(MB)'))


You can see that, we have set the $computername equal to ‘localhost’. That will now serve as a default value. If someone runs this function without specifying a computer name, the function will target ‘localhost’, which is usually a safe operation.

Now, the function can be given any alternative computer name in any of these ways:

Get-ServerInfo -computername vaio

Get-ServerInfo -comp vaio

Get-ServerInfo vaio

Returning Objects from function

Powershell does not really like text – it likes Objects. What we have been doing so far in our function is attempting to format our output as a text table. By doing so we are working against Powershell’s native capabilities, which we are making it harder to get the output we want. That means, we need to stop trying to output texts and instead output objects.

Because we are getting information from two places – Win32_OperatingSystem and Win32_LogicalDisk, we cannot output either of the objects that we got back from WMI. Instead we will create a new blank object [PSObject], that we can use to combine our four pieces of information. We simply need to create a PSObject and add the information in the form of properties.

function Get-ServerInfo {

param (
 $computername = 'localhost'

 $os = Get-WmiObject Win32_OperatingSystem -ComputerName $computername

 $disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ComputerName $computername

 $obj = New-Object -TypeName PSObject

$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $computername
 $obj | Add-Member -MemberType NoteProperty -Name BuildNumber -Value ($os.BuildNumber)
 $obj | Add-Member -MemberType NoteProperty -Name SPVersion -Value ($os.ServicePackMajorVersion)
 $obj | Add-Member -MemberType NoteProperty -Name SysDriveFree -Value ($ / 1MB -as [int])

Write-Output $obj


Get-ServerInfo | Format-Table -AutoSize

Here is what we did:

  • We have simplifying the WMI queries.
  • After querying the information, we create a new PSObject and put  it in a variable.
  • To add information to that object, we pipe the object to Add-Member four times.
  • By using, Write-Output, we are writing the final object into the pipeline.
  • The above code also shows the command we can use to call the function.

The above function is infinitely flexible, because it outputs objects instead of text. Example, all of the below are legitimate ways of using the function.

Get-ServerInfo | Format-Table -AutoSize
Get-ServerInfo -computername vaio | Export-Csv info.csv
Get-ServerInfo -comp localhost | ConvertTo-Html | Out-File info.html


Powershell – InvokeCommand versus ComputerName

As IT Pros, we will be fetching a lot of data from remote computers. But, when we are working in the production environment, we have to pay attention to the load that is exerted by the powershell cmdlets on the machine from which we run the cmdlets.

Choosing the correct cmdlet can make a huge difference in the performance.

To demonstrate this, I am browsing thru 4000 lines of System EventLogs and then selecting only those with the eventId as 1014.

I have two servers. DC and Server1. I am running the commands from Sever1. The reason why I am going thru 4000 entries of EventLog is to have a considerable amount of traffic for the Measure-Command, since the cmdlet has one local system (Server1) and one remote computer (DC).

Now, try running the below cmdlets. If you do not have a set up like this, you may use the Microsoft Virtual Labs

Cmdlet 1:

Measure-Command {Get-EventLog system -ComputerName server1, dc -Newest 4000 | where {$_.EventID -eq 1014}}

Cmdlet 2:

Measure-Command {Invoke-Command -ComputerName dc,server1 -ScriptBlock {Get-EventLog system -Newest 4000 | where {$_.EventID -eq 1014}}}

And below is the result:


If you pay attention to the number of seconds it took for each command to run, it is pretty clear that the “Invoke-Command” has a better performance compared to the cmdlet with “-ComputerName“.

This is because, by using the Invoke-Command, the processing is done locally in the remote computers. But, while using cmdlets with “-ComputerName“, all the data is first fetched to the local system (from where the cmdlet was fired) and then the processing is done locally.

We can see a difference of 4 seconds just for two computers (local and remote). But in reality we will be working with hundreds and thousands of servers, so the difference will be exponential.