Microsoft Virtualization Discussions
Microsoft Virtualization Discussions
Anybody been able to setup a Powershell script to unlock files? I'm thinking something that a user could run to unlock files that are locked under that user's username. We use Citrix, and sometimes a user will lose connection to a session, then not be able to get back to it -- some files are locked open in that other session, and, rather than calling the Helpdesk, etc., it would be useful for that user to unlock certain files just by running a script (e.g., a ~notes.lck file). Thanks.
Solved! See The Solution
Hi,
There is no need to create a share, psfile translates "/vol/vol1/qtree1/database.mdb" as "C:\vol\vol1\qtree1\database.mdb". Here is an example in powershell:
# http://technet.microsoft.com/en-us/sysinternals/bb897552.aspx
# Note that this script relies on "psfile.exe" existing in the windows system directory
#'------------------------------------------------------------------------------
#'Initialization Section. Define Global Variables.
#'------------------------------------------------------------------------------
[String]$uncPath = "\\testns01\test1$\databases\admin\database.mdb"
[String]$scriptPath = Split-Path($MyInvocation.MyCommand.Path)
[String]$scriptSpec = $MyInvocation.MyCommand.Definition
[String]$scriptName = (Get-Item $scriptSpec).Name
[String]$systemPath = [Environment]::SystemDirectory
[String]$exeSpec = "$systemPath\cmd.exe"
[String]$exeName = "psfile.exe"
[Array]$elements = $uncPath -Split [regex]::Escape("\")
[String]$controller = $elements[2]
[String]$shareName = $elements[3]
[String]$fileName = $elements[$elements.Length -1]
#'------------------------------------------------------------------------------
#'construct the folder path between the share name and file name.
#'------------------------------------------------------------------------------
Write-Host "The Script ""$scriptName"" Started Processing."
Write-Host "Processing file ""$uncPath"""
[String]$folderPath = "/"
For($i=4; $i -lt ($elements.Count -1); $i++){
[String]$folderPath = $folderPath + $elements[$i] + "/"
}
#'------------------------------------------------------------------------------
#'Ensure psfile.exe exists in the scripts system directory.
#'------------------------------------------------------------------------------
If(-Not(Test-Path -Path "$systemPath\$exeName")){
Write-Host "The file ""$systemPath\$exeName"" does not exist"
Break;
}
#'------------------------------------------------------------------------------
#'Enumerate the mount point of the share and file
#'------------------------------------------------------------------------------
Import-Module DataOnTap
Connect-NaController -Name $controller | Out-Null
[String]$mountPoint = Get-NaCifsShare | Where-Object {$_.ShareName -eq $shareName} | Select-Object -ExpandProperty MountPoint
[String]$mountPath = "$mountPoint$folderPath$fileName"
$file = Get-NaLockStatus | Where-Object {$_.Mode -eq "Oplock-Excl" -And $_.Path -eq $mountPath}
[String]$filePath = $file.Path
[String]$owner = $file.Owner
Write-Host "Enumerated the mount point for CIFS share ""$shareName"" as ""$mountPoint"""
Write-Host "Enumerated CIFS Locked file as ""$filePath"" locked by ""$owner"""
#'------------------------------------------------------------------------------
#'Replace the foward slash with back slash characters to build the local file path.
#'------------------------------------------------------------------------------
[String]$fileSpec = "C:" + ($filePath -Replace ([regex]::Escape("/"),"\"))
[String]$command = "$exeName \\$controller ""$fileSpec"" -c"
Start-Process $exeSpec -ArgumentList " /c $command"
Write-Host "Executed Command: $command"
Write-Host "The Script ""$scriptName"" Completed Successfully."
Here is an example of the script output:
PS C:\Scripts\PowerShell\Projects\CloseLockedFile> .\CloseLockedFile.ps1
The Script "test.ps1" Started Processing.
Processing file "\\testns01\test1$\databases\admin\database.mdb"
Enumerated the mount point for CIFS share "test1$" as "/vol/vol1/qtree1"
Enumerated CIFS Locked file as "/vol/vol1/qtree1/Databases/Admin/database.mdb" locked by "administrator"
Executed Command: psfile.exe \\testns01 "C:\vol\vol1\qtree1\Databases\Admin\database.mdb" -c
The Script "CloseLockedFile.ps1" Completed Successfully.
PS C:\Scripts\PowerShell\Projects\CloseLockedFile>
Hope that helps
Cheers Matt
Hey,
Actually I'm working on a solution to do this right...there appears to be a cmdlet to unlock NFS locks but no CIFS??? If you want to provide standard users with the ability to do this then you will probably need to have a service account run the script as a scheduled task or use an orchestration tool like NetApp WFA product to build a workflow that accepts the UNC path of the file to close. At a first glance it will probably require a few cmdlet's to translate the UNC path to mount point and find out who has the file locked
Get-NaLockStatus [[-Protocol] <String>] [[-FileName] <String>]
Get-NaCifsShare [[-Share] <String>] [-Controller <NaController>] [<CommonParameters>]
$command = “lock break -p cifs -f /vol/vol1/qtree1/file.mdb”
Invoke-NaSsh $command
Watch this space...I'll post back when i've had time to develop something
Cheers Matt
Sounds great! I'll check out NetApp WFA and will keep an eye on this space.
Would the service account need to have root / administrator access on the filer? That's another, related issue that I've been trying to solve -- provide the permission to unlock files, but not provide full admin. This would be especially important if running a script, but your "scheduled task" idea sounds good -- just allow users the ability to start the task, which itself runs under admin credentials.
Thanks!
Hey...
So it appears that there is a known problem with the powershell toolkit when you execute the "invoke-nassh" cmdlet using the "lock break -f" command. The ZAPI command on the controller prompts for confirmation and there is not currently a workaround for this issue with the "invoke-nassh" cmdlet...however you can use a combination of Get-NaCifsShare and Get-NaLockStatus with the psfile.exe utility from system internals to close the file. Here is the syntax (assumes it is executed with an account with admin privellages on the filer...i think this may be delegated to a member of Power Users)
C:\>psfile.exe \\testns01 C:\vol\vol1\qtree1\database.mdb
psfile v1.02 - psfile
Copyright ® 2001 Mark Russinovich
Sysinternals
Files opened remotely on testns01 matching C:\vol\vol1\qtree1\database.mdb:
[207] C:\vol\vol1\qtree1\database.mdb
User: administrator
Locks: 0
Access: Read Write
C:\>psfile \\testns01 C:\vol\vol1\qtree1\database.mdb -c
psfile v1.02 - psfile
Copyright ® 2001 Mark Russinovich
Sysinternals
Closed file C:\vol\vol1\qtree1\database.mdb on testns01.
Keep an eye out and i'll post something when i've had time to convert it into a powershell script.
Matt
Thanks.
How'd you share c:\ on the filer, so that the path c:\vol\vol1\qtree1\database.mdb would work when accessed from a Windows machine?
Hi,
There is no need to create a share, psfile translates "/vol/vol1/qtree1/database.mdb" as "C:\vol\vol1\qtree1\database.mdb". Here is an example in powershell:
# http://technet.microsoft.com/en-us/sysinternals/bb897552.aspx
# Note that this script relies on "psfile.exe" existing in the windows system directory
#'------------------------------------------------------------------------------
#'Initialization Section. Define Global Variables.
#'------------------------------------------------------------------------------
[String]$uncPath = "\\testns01\test1$\databases\admin\database.mdb"
[String]$scriptPath = Split-Path($MyInvocation.MyCommand.Path)
[String]$scriptSpec = $MyInvocation.MyCommand.Definition
[String]$scriptName = (Get-Item $scriptSpec).Name
[String]$systemPath = [Environment]::SystemDirectory
[String]$exeSpec = "$systemPath\cmd.exe"
[String]$exeName = "psfile.exe"
[Array]$elements = $uncPath -Split [regex]::Escape("\")
[String]$controller = $elements[2]
[String]$shareName = $elements[3]
[String]$fileName = $elements[$elements.Length -1]
#'------------------------------------------------------------------------------
#'construct the folder path between the share name and file name.
#'------------------------------------------------------------------------------
Write-Host "The Script ""$scriptName"" Started Processing."
Write-Host "Processing file ""$uncPath"""
[String]$folderPath = "/"
For($i=4; $i -lt ($elements.Count -1); $i++){
[String]$folderPath = $folderPath + $elements[$i] + "/"
}
#'------------------------------------------------------------------------------
#'Ensure psfile.exe exists in the scripts system directory.
#'------------------------------------------------------------------------------
If(-Not(Test-Path -Path "$systemPath\$exeName")){
Write-Host "The file ""$systemPath\$exeName"" does not exist"
Break;
}
#'------------------------------------------------------------------------------
#'Enumerate the mount point of the share and file
#'------------------------------------------------------------------------------
Import-Module DataOnTap
Connect-NaController -Name $controller | Out-Null
[String]$mountPoint = Get-NaCifsShare | Where-Object {$_.ShareName -eq $shareName} | Select-Object -ExpandProperty MountPoint
[String]$mountPath = "$mountPoint$folderPath$fileName"
$file = Get-NaLockStatus | Where-Object {$_.Mode -eq "Oplock-Excl" -And $_.Path -eq $mountPath}
[String]$filePath = $file.Path
[String]$owner = $file.Owner
Write-Host "Enumerated the mount point for CIFS share ""$shareName"" as ""$mountPoint"""
Write-Host "Enumerated CIFS Locked file as ""$filePath"" locked by ""$owner"""
#'------------------------------------------------------------------------------
#'Replace the foward slash with back slash characters to build the local file path.
#'------------------------------------------------------------------------------
[String]$fileSpec = "C:" + ($filePath -Replace ([regex]::Escape("/"),"\"))
[String]$command = "$exeName \\$controller ""$fileSpec"" -c"
Start-Process $exeSpec -ArgumentList " /c $command"
Write-Host "Executed Command: $command"
Write-Host "The Script ""$scriptName"" Completed Successfully."
Here is an example of the script output:
PS C:\Scripts\PowerShell\Projects\CloseLockedFile> .\CloseLockedFile.ps1
The Script "test.ps1" Started Processing.
Processing file "\\testns01\test1$\databases\admin\database.mdb"
Enumerated the mount point for CIFS share "test1$" as "/vol/vol1/qtree1"
Enumerated CIFS Locked file as "/vol/vol1/qtree1/Databases/Admin/database.mdb" locked by "administrator"
Executed Command: psfile.exe \\testns01 "C:\vol\vol1\qtree1\Databases\Admin\database.mdb" -c
The Script "CloseLockedFile.ps1" Completed Successfully.
PS C:\Scripts\PowerShell\Projects\CloseLockedFile>
Hope that helps
Cheers Matt
It might help if I used the correct path.... Email had been upgraded recently, and I forgot that the path to the ~notes.lck file is different. After using the correct path, I was able to close the files with psfile as my non-admin user. I confirmed, though, that at least Power Users privileges on the filer are required. Rather than making all users part of the Power Users group on the filer, I'll likely create a local user on the filer specifically for the purpose of running my psfile script.
Thanks for all the help, mbeattie!