2
votes

I am trying to write VBScript that will check for an open file on our file share then close the session to the file if found. The problem is that the object's session ID comes back negative sometimes, so it does not always work. When comparing it to the actual session ID using Net File, it is completely different. Below is the code.

If LCase(Right(Wscript.FullName, 11)) = "wscript.exe" Then
    strPath = Wscript.ScriptFullName
    strCommand = "%comspec% /k cscript """ & strPath & """"
    Set objShell = CreateObject("Wscript.Shell")
    objShell.Run(strCommand), 1, True
    Wscript.Quit
End If

intFound = 0

Set objNetwork = CreateObject("WScript.Network")
Set objShell = CreateObject("WScript.Shell")

strDomain = objNetwork.UserDomain
strServerName = "myServerName"
strFileToClose = "Some Open File.PDF"

Set objConnection = GetObject("WinNT://" & strDomain & "/" & strServerName & "/LanmanServer")
Set objOpenFiles = objConnection.Resources

WScript.Echo "Files open on " & strServerName & VbCrLf & "=============================="
strIDs = ""

For Each objFile In objOpenFiles
    On Error Resume Next
    temp = objFile.User
If Err.Number = 0 Then
    On Error GoTo 0
    If InStr(LCase(objFile.Path), LCase(strFileToClose)) > 0 Then
        WScript.Echo objFile.Name & " || " & objFile.Path & " || " & objFile.User
        If strIDs = "" Then
            strIDs = objFile.Name
        Else
            strIDs = strIDs & ";" & objFile.Name
        End If
        intFound = intFound + 1
    End If
Else
    Err.Clear
    On Error GoTo 0
End If
Next

WScript.Echo ""

If intFound > 0 Then
    arrIDs = Split(strIDs, ";")
    For Each strFileID In arrIDs
        strCommand = "cmd /c Net File " & strFileID & " /close"
        objShell.Run strCommand, 0, True
        WScript.Echo strFileID & " has been closed."
    Next
Else
    WScript.Echo "No matching open files were found."
End If

Here is an example of the output with a negative session ID from the script then the actual results from Net File. Keep in mind that some of the time they match and other times they do not!

Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation. All rights reserved.

Files open on myServerName
==============================
-2013150206 || C:\\Users\user\Desktop\Some Open File.PDF || user

-2013150206 has been closed. 'Not true since it is a negative number

C:\Users\admin\Desktop>net file 2281817090 'Actual Session ID
File ID         2281817090
User name       user
Locks           0
Path            C:\\Users\user\Desktop\Some Open File.PDF
Permissions     R
The command completed successfully.


C:\Users\admin\Desktop>

What I believe is going on is that the variant's datatype is getting changed in that if the number is big enough it will get stored as a negative value... I do not think you can change it to an unsigned integer, and I could be completely wrong in assuming I even know what is going on.

2

2 Answers

0
votes

I can reproduce your problem with negative file ID (seems to be a Microsoft product bug because all documents I have found say that Name/ID property should be a string).
Next commented Powershell script shows a workaround: adding 4,294,967,296 i.e. 2^32 value to compensate 32-bit arithmetic overflow.

$server  = "$env:COMPUTERNAME"                      # change to meet your terms

$netfile = [ADSI]"WinNT://$server/LanmanServer"     # bind to an ADSI object
$netfile.Invoke("Resources") |
  foreach {$collection = @()} {                    <# create and load an array #>
    $collection += New-Object PsObject -Property @{
        Id = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
        UserName = $_.GetType().InvokeMember("User", 'GetProperty', $null, $_, $null)
        LockCount = $_.GetType().InvokeMember("LockCount", 'GetProperty', $null, $_, $null)
        Server = $server
        Path = $_.GetType().InvokeMember("Path", 'GetProperty', $null, $_, $null)
    }
}
$collection |
  Where-Object {([long]$_.Id) -lt 0} |               <# for each negative Id  #>
    Format-Table -AutoSize -Property Id, Path        <# show result: Id, Path #>
$collection | 
  Where-Object {([long]$_.Id) -lt 0} |               <# for each negative Id  #>
    ForEach-Object {
      $IdPlus = [string]([long]$_.Id + 0x100000000)   # add 4294967296
                                                      # and call `net` command
      & cmd /C net file $IdPlus `| findstr `/V "^User successfully.$ ^Locks ^Permissions"
    }

Output (Copy&Paste from elevated PowerShell window):

Windows PowerShell
Copyright (C) 2014 Microsoft Corporation. All rights reserved.

PS C:\Windows\system32> D:\PShell\SO\39196152a.ps1

Id         Path
--         ----
-201326469 C:\attachments
-402652947 C:\$AVG
-402652182 D:\Downloads\Batch-File-Programming.pdf


File ID         4093640827
Path            C:\attachments

File ID         3892314349
Path            C:\$AVG

File ID         3892315114
Path            D:\Downloads\Batch-File-Programming.pdf

PS C:\Windows\system32>

Please note that

  • in Powershell: [long] numeric type means [int64]; on the other hand,
  • in VBScript: [long] Variant subtype means [int32]; you need to use [double] subtype to hold values out of the range from -2,147,483,648 to +2,147,483,647, see CDbl() function:

Use the CDbl function to provide conversions from any data type to a Double subtype. For example, CDbl forces double-precision arithmetic when currency or integer arithmetic would normally occur.

Further resources:

0
votes

I ended up writing a different solution with VBA that works with our current task system, and is able to reliably extract file session ID's based on a specific file name, and close them all.

Dim strFileToClose As String
Dim strLine As String
Dim strSession As String
Dim time1, time2
Sub main()

'Initialize strings as empty
strFileToClose = ""
strLine = ""
strSession = ""

strFileToClose = "Some Open File.PDF"

'Update the text file of open file sessions on the server
Call Shell("cmd /c c:\psexec.exe \\remoteServerName cmd /c net file > c:\temp\file_sessions.txt", vbNormalFocus)

'Need to add a delay before we parse the text file
time1 = Now
time2 = Now + TimeValue("0:00:03")

Do Until time1 >= time2
    time1 = Now()
Loop

'Parse the text file for our open file session ID
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objOutput = objFSO.OpenTextFile("c:\temp\file_sessions.txt", 1)

Do Until objOutput.AtEndOfStream
    strLine = objOutput.ReadLine()

    If InStr(LCase(strLine), LCase(strFileToClose)) > 0 Then
        'Extract just the file session ID from the parse
        strSession = Split(strLine, " ")(0)
        'MsgBox strSession

        'Take the found session ID from the text file and use it to close the session
        Call Shell("cmd /c c:\psexec.exe \\remoteServerName cmd /c net file """ & strSession & """ /close", vbNormalFocus)

    End If

Loop
'Close the text file once it's done being read
objOutput.Close

End Sub