Troubleshooting a Windows Powershell Script

Hey, Scripting Guy! QuestionHey Scripting Guy! I tried to use the script from the May 2009 TechNet Magazine article, Hey, Scripting Guy! Working with Access Databases in Windows PowerShell. But when I run it I get an error. This happens both on my Windows Vista  computer (64 bit, Service Pack 2) with Office 2007 SP2, and my Windows 2007 build 7100 (64 bit) with Office 2007 SP2 installed. The database I am using has been created by hand. Here is the error I am receiving.

Exception calling "Open" with "1" argument(s): "Provider cannot be found. It may not be properly installed."

At C:\Users\fvandonk\AppData\Local\Temp\Untitled1.ps1:31 char:19

+   $connection.Open <<<< ("Provider = Microsoft.Jet.OLEDB.4.0;Data Source=$Db" )


I get a similar error when I create the database using the script from the Hey Scripting Guy! Article, How Can I Create a Database with More Than One Table? The error from that script is:

Unexpected token 'Provider=' in expression or statement

Can you tell me what I am doing wrong?

-- FD

 

Hey, Scripting Guy! AnswerHello FD,

The problem is that when I wrote those two scripts, I was using a 32-bit operating system. I have now upgraded to Windows 7 64-bit, and I get the same error messages you are getting. To run the scripts without generating an error, you need to make sure you are using the 32-bit version of Windows PowerShell. On a 64-bit system, you will find both a 32-bit version and a 64-bit version of Windows PowerShell installed. You should see the link for 32-bit Windows PowerShell off the Start menu on Windows 7. Click Accessories and then click Windows PowerShell:

Image of Windows PowerShell location in Windows 7


I used Windows PowerShell ISE (x86) and it works fine. I would consider putting something like this at the beginning of the script:

if($env:PROCESSOR_ARCHITECTURE -ne 'x86')

  { 'This script must be run in x86 powershell' ; exit}

 


Outputting Only the Last Three Lines of a Command

Hey, Scripting Guy! QuestionHey Scripting Guy! I have a request which I believe should be very simple, but all the searching of the Internet has not turned up anything quite right. How can I output only the last three lines of a command into a text document?

For example, I am using the command dir /s to find the total size and number of files and folders in a given directory. These directories can be quite large, so a complete output may cause the text file to exceed 1 GB in size. All I really need is the last three lines that read:

     Total Files Listed:
           299174 File(s)  1,238,631,539 bytes
           27731 Dir(s)  1,667,751,895,040 bytes free

-- KS

Hey, Scripting Guy! AnswerHello KS,

I wrote the GetDirectoryListingStats.vbs script to illustrate what you will need to do. In the GetDirectoryListingStats.vbs script, you first create an instance of the WshShell object as seen here:

Set objShell = CreateObject("WScript.Shell")


You then call the exec method to execute the dir command. To be able to execute the dir command, you also need to call the command interpreter. This is seen here:

Set objExecObject = objShell.Exec(command)


The exec method returns a textstream object. To walk through the textstream object, use the Do Until…Loop as seen here:

Do Until objExecObject.StdOut.AtEndOfStream

    strText = objExecObject.StdOut.ReadAll

Loop


Next, you use the split function to break the output into an array that will allow you to return the last few lines. This is shown here:

aryText = Split(strText, VbNewLine)


The complete GetDirectoryListingStats.vbs script is seen here.

GetDirectoryListingStats.vbs

'==========================================================================

'

' NAME: GetDirectoryListingStats.vbs

'

' AUTHOR: Ed Wilson , Microsoft

' DATE  : 6/18/2009

'

' COMMENT: Uses the exec method from the WshShell object to execute a command

' Uses stdOut to read the output.

'

'==========================================================================

 

'Option Explicit  'is used to force the scripter to declare variables

'On Error Resume Next  ‘ go to the next line if it encounters an Error

Dim objShell  'holds WshShell object   

Dim objExecObject  'holds what comes back from executing the command

Dim strText  'holds the text stream from the exec command.

Dim command  'the command to run

Dim Directory  'directory to query

Directory = "C:\fso"

command = "cmd /c dir " & Directory

WScript.echo "starting program " & Now ' used to mark when program begins

Set objShell = CreateObject("WScript.Shell")

Set objExecObject = objShell.Exec(command)

 

Do Until objExecObject.StdOut.AtEndOfStream

    strText = objExecObject.StdOut.ReadAll

Loop

aryText = Split(strText, VbNewLine)

wscript.Echo "There are " & ubound(aryText) & _

 " items in the " & Directory & " Folder"

WScript.Echo aryText(ubound(aryText) -2 )

WScript.Echo aryText(ubound(aryText) -1)

 
The Measure-Object Cmdlet in Windows PowerShell CTP3

Hey, Scripting Guy! QuestionHey Scripting Guy! The –line switch used with Measure-Object in Windows PowerShell CTP3 seems to only count nonempty lines in .txt files. Is that the supposed behavior?

-- TB

Hey, Scripting Guy! AnswerHello TB,

Let’s take a look. First use the Get-Content cmdlet to read the contents of a text file. This is seen here:

PS C:\Users\edwils> Get-Content 'C:\fso\New Text Document.txt'

line one

 

line two

 

line three

We see there are five lines in the text file, but only three have text. Now use the Get-Content cmdlet and pipeline the results to the Measure-Object cmdlet, and tell it to  count the number of lines. This is seen here:

PS C:\Users\edwils> Get-Content 'C:\fso\New Text Document.txt' | Measure-Object -Line

 

                        Lines Words                         Characters                    Property

                        ----- -----                         ----------                    --------

                            3


As you can see, Measure-Object tells us we have three lines. Lets now try one more thing: Use Get-Content and store the contents into a text file. Now use the count property on the file. This is seen here:

PS C:\Users\edwils> $file = Get-Content 'C:\fso\New Text Document.txt'

PS C:\Users\edwils> $file.Count

5


TB you are correct. The Measure-Object cmdlet only counts lines; it does not count blank lines. If you need to get blank lines in a text file, you can use the count property.



Troubleshooting Output from a VBScript Script

Hey, Scripting Guy! QuestionHey Scripting Guy! I am having an issue with outputting results. My code is reading server names from a .txt file and outputting the logged-on terminal server users and the server name they are logged onto. My code looks like this:

GetTerminalServerUsersAndServers.vbs

Const ForReading = 1

Const wbemImpersonationLevelImpersonate = 3

Const wbemAuthenticationLevelPktPrivacy = 6

 

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objTextFile = objFSO.OpenTextFile("servers.txt", ForReading)

 

Do Until objTextFile.AtEndOfStream

    strComputer = objTextFile.Readline

    Set objLocator = CreateObject("WbemScripting.SWbemLocator")

    Set objWMI = objLocator.ConnectServer (strComputer, "root\cimv2")

    objWMI.Security_.ImpersonationLevel = wbemImpersonationLevelImpersonate

    objWMI.Security_.AuthenticationLevel = wbemAuthenticationLevelPktPrivacy

    Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

    Set colSessions = objWMI.ExecQuery _

        ("Select * from Win32_LogonSession Where LogonType = 10")

          If colSessions.Count = 0 Then

             Wscript.Echo "No interactive user found"

          Else

           For Each objSession in colSessions                 

            Set colList = objWMI.ExecQuery("Associators of " _

              & "{Win32_LogonSession.LogonId=" & objSession.LogonId & "} " _

              & "Where AssocClass=Win32_LoggedOnUser Role=Dependent" )                                                                      

            For Each objItem in colList

             Wscript.Echo "Server Name: " & strComputer

             WScript.Echo objItem.Name                        

            Next                                

           Next

          End If

Loop

The problem is with the output. It is coming out like this (shortened):

Server Name: ctx-07

carrie

Server Name: ctx-07

diane

Server Name: ctx-07

rachel

Server Name: ctx-09

sandra

Server Name: ctx-09

kathy

Server Name: ctx-09

Linda

Instead, I would like to see the users group by server, as seen here:

Server Name: ctx-07

carrie

diane

rachel

Server Name: ctx-09

sandra

kathy

Linda


I think I have an idea how to do thiswith some kind of Do loopbut I am not confident my solution is correct. Could you point me in the right direction?

 

-- WM

Hey, Scripting Guy! AnswerHello WM,

Your problem is that you are emitting the data from inside the For…Each…Next loop. Each time you pass through the loop, the data is being displayed. Instead, what you need to do is capture the information from inside the loop and wait until you are done looping to display the information. Something like this would be relatively easy to do:

GetTerminalServerUsersAndServersRevised.vbs

Const ForReading = 1

Const wbemImpersonationLevelImpersonate = 3

Const wbemAuthenticationLevelPktPrivacy = 6

 

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objTextFile = objFSO.OpenTextFile("servers.txt", ForReading)

 

Do Until objTextFile.AtEndOfStream

    strComputer = objTextFile.Readline

    Set objLocator = CreateObject("WbemScripting.SWbemLocator")

    Set objWMI = objLocator.ConnectServer (strComputer, "root\cimv2")

    objWMI.Security_.ImpersonationLevel = wbemImpersonationLevelImpersonate

    objWMI.Security_.AuthenticationLevel = wbemAuthenticationLevelPktPrivacy

    Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

    Set colSessions = objWMI.ExecQuery _

        ("Select * from Win32_LogonSession Where LogonType = 10")

          If colSessions.Count = 0 Then

             Wscript.Echo "No interactive user found"

          Else

           For Each objSession in colSessions                 

            Set colList = objWMI.ExecQuery("Associators of " _

              & "{Win32_LogonSession.LogonId=" & objSession.LogonId & "} " _

              & "Where AssocClass=Win32_LoggedOnUser Role=Dependent" )                                                                       

            For Each objItem in colList

              colNames = colNames & VBCRLF & objItem.Name    

            Next                                

           Next

          End If

        Wscript.Echo "Server Name: " & strComputer

        Wscript.Echo colNames

Loop

We come to the end of another Quick-Hits Friday, which also brings to an end another week at the Script Center. Join us on Monday for another fun-filled and exciting week in the world of scripting. Until then, have a great weekend.

Ed Wilson and Craig Liebendorfer, Scripting Guys