Showing posts with label VBScript. Show all posts
Showing posts with label VBScript. Show all posts

Thursday, July 28, 2011

The last VBScript you will ever need?

One of the first things people notice when writing their very first PowerShell scripts is that clicking the script file won’t run it, but instead open up Notepad. Which is nice, if you want to open up Notepad to read the script.
But I’m guessing that’s not why you clicked it.

There are of course several different ways to launch a PowerShell script. One is to run powershell.exe and specify the script as one of the arguments. This is useful if you want to run the script from a batch file, a shortcut, a scheduled task or wherever else you can specify a program to run.

I figured it would be neat to make the PowerShell script “clickable” by creating a shortcut to it and tell it to be run by powershell.exe. But doing this by hand is tedious work! (And as we all know, tedious work is the very reason you started scripting in the first place.)

Enter: VBScript

It’s easy to create a shortcut using VBScript, and it’s piece of cake to drag and drop a file onto another. –Which is something VBScript gladly accepts, taking the dropped file’s path as an argument to pass to the script. Knowing this, we can put two and two together, and write a script which allows you to drop a .ps1 file onto it, and then have it create a shortcut for us!

Now, this is a very straight forward script, which takes one file and creates a shortcut to it, adding the necessary parameters. It can be modified to allow you to drop several files onto it, or to run powershell.exe with additional parameters (such as setting execution policy).

Option Explicit

If WScript.Arguments.Count = 0 Then
    WScript.Echo "Drag and drop a .ps1 file onto the script."
    WScript.Quit
End If

Dim strPowerShellScriptFullPath : strPowerShellScriptFullPath = WScript.Arguments.Item(0)
Dim objFSO : Set objFSO = CreateObject("Scripting.FileSystemObject")

If Not objFSO.FileExists(strPowerShellScriptFullPath) Then
    WScript.Quit
End If
If Not LCase(objFSO.GetExtensionName(strPowerShellScriptFullPath)) = "ps1" Then
    WScript.Quit
End If

Dim strPowerShellScriptPath : strPowerShellScriptPath = Left(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim strPowerShellScript : strPowerShellScript = Mid(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim objShortCut : Set objShortCut = objShell.CreateShortcut(strPowerShellScriptPath & strPowerShellScript & " - Shortcut.lnk")

objShortCut.TargetPath = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
objShortCut.Arguments = "-NoExit -File """ & strPowerShellScriptFullPath & """"
objShortCut.WorkingDirectory = "%HOMEDRIVE%%HOMEPATH%"
objShortCut.IconLocation = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe, 0"
objShortCut.Description = "Run PowerShell script"
objShortCut.Save

I named my script “Create PowerShell Shortcut.vbs”.
And as always, there’s a very real risk of lines wrapping where they shouldn’t. If anything other than a .ps1 file is dropped onto it, nothing will happen (not even a message, in the above example).

When a ps1 file is dropped onto it, the script will create a shortcut to powershell.exe and assign the ps1 file path as an argument. The shortcut will be created in the same location as the ps1 file was dragged from. The VBScript and ps1 file does not have to be in the same location.


(It can of course also be run from the command line, as long as you pass a proper path, but that sort of defeats the purpose of it, now doesn’t it..)

Sunday, April 3, 2011

VBScript - WMI Ping

Sometimes it can come in handy to actually check, instead of just assume, that a destination is available before you let your script loose. One method you can use for this is ping. I recently wrote a log on script which pings a server before it attempts to map a network drive.

That script used a very simple, but yet effective, function that returned true or false based on the result. If ping failed (the function returned false), it would continue to try for 90 seconds before giving up. Here’s that little piece of code:

Function Ping(address)
    Ping = False
    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set objPing = objWMI.Get("Win32_PingStatus.Address='" & address & "'")
    If objPing.StatusCode = 0 Then
        Ping = True
    End If
End Function

WScript.Echo Ping("google.com")

To test this function, save it in a text file with the file extension .vbs, and run the file. It should return either –1 (true) or 0 (false) to the screen.

But perhaps you want a bit more information than that, since the above script was not made for human readability, but rather to be used by other functions within the script. 

So I put together two more functions for this purpose. PingStatusCode and PingStatus. The former will get the return code and the latter will translate the code to text, when it is passed to it.

Function PingStatusCode(address)
    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set objPing = objWMI.Get("Win32_PingStatus.Address='" & address & "'")
    PingStatusCode = objPing.StatusCode
End Function

Function PingStatus(PingStatusCode)
    Select Case Int(PingStatusCode)
        Case Int(0)
            PingStatus = "Success"
        Case Int(11001)
            PingStatus = "Buffer Too Small"
        Case Int(11002)
            PingStatus = "Destination Net Unreachable"
        Case Int(11003)
            PingStatus = "Destination Host Unreachable"
        Case Int(11004)
            PingStatus = "Destination Protocol Unreachable"
        Case Int(11005)
            PingStatus = "Destination Port Unreachable"
        Case Int(11006)
            PingStatus = "No Resources"
        Case Int(11007)
            PingStatus = "Bad Option"
        Case Int(11008)
            PingStatus = "Hardware Error"
        Case Int(11009)
            PingStatus = "Packet Too Big"
        Case Int(11010)
            PingStatus = "Request Timed Out"
        Case Int(11011)
            PingStatus = "Bad Request"
        Case Int(11012)
            PingStatus = "Bad Route"
        Case Int(11013)
            PingStatus = "TimeToLive Expired Transit"
        Case Int(11014)
            PingStatus = "TimeToLive Expired Reassembly"
        Case Int(11015)
            PingStatus = "Parameter Problem"
        Case Int(11016)
            PingStatus = "Source Quench"
        Case Int(11017)
            PingStatus = "Option Too Big"
        Case Int(11018)
            PingStatus = "Bad Destination"
        Case Int(11032)
            PingStatus = "Negotiating IPSEC"
        Case Int(11050)
            PingStatus = "General Failure"
        Case Else
            PingStatus = "Unknown Error"
    End Select
End Function

You can test the functions by adding these lines of code, outside the functions, before you run the script:

WScript.Echo PingStatusCode("google.com") & vbTab & PingStatus(PingStatusCode("google.com"))
WScript.Echo PingStatusCode("microsoft.com") & vbTab & PingStatus(PingStatusCode("microsoft.com"))

The script will first ping google.com and give you the return code and message, then it will ping microsoft.com and, again, give you the return code and message.

 

Here’s the MSDN reference for the Win32_PingStatus Class:
http://msdn.microsoft.com/en-us/library/aa394350(v=VS.85).aspx

Wednesday, March 23, 2011

PowerShell - Enable Exchange 2010 Mailbox from Active Directory

I installed Windows Live Writer the other day, so I figured I’d finally give it a shot. Step one was to load up with four cheese sandwiches, a cup of tea and a script! Earlier today when I pondered what to write about, I was deciding between managing NTFS inheritance with PowerShell or using redircmp to better control new domain computers.

Turns out I won’t be writing about either. In this post.

Lets instead turn our focus to Active Directory (AD) and Exchange 2010. Anyone who has worked with AD and Exchange before 2007 will most likely be familiar with the additional tabs in the Active Directory Users and Computers properties sheet for managing Exchange settings. These are gone in Exchange 2010, and as far as I know, won’t be coming back. All the Exchange management is now done in the Exchange Management Console (EMC). While EMC does allow you to create AD objects and mail/mailbox enable them, I find it a but cumbersome if all you want to do is create a mailbox for a user account.

One really nice touch with EMC is that it provides you with the PowerShell (PS) command to do <whatever it is you’re configuring>. Mailbox enabling a user account for example. This means if you’ve done it once in EMC you can easily do it in PowerShell instead. If you don’t mind typing or firing up a PS script to do this, it’s all good. But that’s not why we’re here.

Lets think back to the Exchange 2003 days and how we used to mailbox enable users by right clicking and going to “Exchange Tasks” and then selected what we wanted to do, and went through the wizard.

This is more like it!

Is there something similar in Exchange 2010? No. Not as far as I know (feel free to correct me). But if there’s a will, there’s a way! Since we already have the PowerShell command to mailbox enable the user, we can take advantage of the AD display specifiers and add our own menu item that will appear when you right click a user account. All it will take is a little bit of VBScript magic and an update of a configuration attribute in Active Directory.

We’ll start out by writing a VBScript. The script will perform a couple of checks to make sure that the user account is enabled (it’s a requirement for the PowerShell command), and that it isn’t already mail or mailbox enabled. Then it will proceed by launching PowerShell and finally execute the “Enable-Mailbox” cmdlet.

When a context menu item is selected, the object (ADsPath) itself is passed as an argument to the program.

Enable-Mailbox.vbs

Option Explicit
Dim objShell, objArgs, objUser

Set objShell = WScript.CreateObject("WScript.Shell")
Set objArgs = WScript.Arguments
Set objUser = GetObject(objArgs(0))

If Not IsEmpty(objUser.legacyExchangeDN) Then
    MsgBox "Object " & objUser.CN & " is already mail or mailbox enabled.", vbInformation, "Active Directory Domain Services"
    WScript.Quit
End If

If objUser.AccountDisabled = True Then
    MsgBox "Object " & objUser.CN & " is disabled. Please enable before proceeding.", vbInformation, "Active Directory Domain Services"
    WScript.Quit
End If

objShell.Run("powershell.exe -noexit Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010; Enable-Mailbox -Identity '" & objUser.distinguishedName & "' -Alias '" & objUser.sAMAccountName & "'")

Set objArgs = Nothing
Set objUser = Nothing
Set objShell = Nothing

As always, watch out for unintentional line breaks.

Save this script and place it in a suitable location. I put mine in the \\testlab.local\NETLOGON share for high availability.

The next step is to edit the user account display specifier in the Active Directory configuration partition. I used ADSIEdit.msc to do this. Open it up and connect to the configuration naming context. The attribute is found in this location:
CN=user-Display,CN=409,CN=DisplaySpecifiers,CN=Configuration,DC=testlab,DC=local

At the top you will find an attribute called “adminContextMenu”. We have to edit this attribute to add our entry to the context menu. Leave any entry already there, and add a new one. Mine looks like this:
2,Enable Mailbox,\\testlab.local\NETLOGON\Enable-Mailbox.vbs

The first position is simply where in the menu it should appear. I only had one entry, so I picked number 2. The second position is the name you want to appear in the context menu. Prefixing a letter with the ampersand character (&) adds a shortcut to it. The third entry is the location of the script. In this case, Enable-Mailbox.vbs in the netlogon share.

This MSDN article talks a bit more about the adminContextMenu: http://msdn.microsoft.com/en-us/library/ms677915(v=vs.85).aspx

Once applied, start or restart the Active Directory Users and Computers mmc and look up a user without a mailbox. Right click the user and now “Enable Mailbox” should appear. If you click it, PowerShell will start and load “Microsoft.Exchange.Management.PowerShell.E2010” (this must be present for this to work), and then run the cmdlet to enable a mailbox for the user account!

This should work if the EMC is installed on the system. If you’re trying to do this remote, you may have to use a remote pssession to first connect to a suitable server. I haven’t tried this yet, since right now there are no clients in my lab. –Maybe I’ll revisit this later.

 

As always, try this out in a lab first to ensure it behaves as expected, before you go live.

Monday, March 14, 2011

VBScript - Desktop Shortcut

A quick one today, if you ever have the need to create a desktop shortcut using VBScript. This can be deployed in a user logon script (GPO or AD) for example.


Set objShell = CreateObject("WScript.Shell")
strDesktop = objShell.SpecialFolders("Desktop")
Set objLink = objShell.CreateShortcut(strDesktop & "\Andreas - Talk nerdy to me.lnk")
objLink.TargetPath = "C:\Program Files\Internet Explorer\iexplore.exe"
objLink.Arguments = "http://ahultgren.blogspot.com/"
objLink.WorkingDirectory = "%HOMEDRIVE%%HOMEPATH%"
objLink.IconLocation = "C:\Program Files\Internet Explorer\iexplore.exe, 2"
objLink.Description = "Andreas - Talk nerdy to me!"
objLink.Save

This link fires up Internet Explorer and goes straight to my blog. It should be fairly obvious 
what the different methods do and how you change them. 


Wednesday, March 9, 2011

DFS - What's that namespace?

I have on several occasions made an (albeit half-hearted) attempt to figure out how to check what DFS mode (2000 or 2008) a domain namespace (DFSN) is using. Last week I finally decided to get to the bottom of it.

If your domain functional level (DFL) is lower than 2008, then this should not be an issue. Windows Server 2000 mode is the only option.

However, if your DFL is Windows Server 2008, then you have an option to create a namespace using either Windows Server 2000 or 2008 mode. You can see which mode a namespace is using if you open the DFS Management console. Right click a namespace, click properties, and the mode is listed on the "General" tab. It can also be seen in the description bar if a namespace is selected.

But this is not what I wanted. I wanted a way to retreive this information using the command line.

The command line tools available for DFS management are dfscmd, dfsutil and dfsdiag. I checked the documentation and help/syntax for a way to display the DFSN mode. I found nothing.

I searched the Internet and posted in a forum on technet, but it bore no fruit.


I fired up ldp.exe and adsiedit to look for any clues, in case the information was stored there (the mmc console obviously knew which mode a namespace was in!).

The DFS configuration can be found here: 
CN=Dfs-Configuration,CN=System,DC=testlab,DC=local

Here I found the namespace attribute msDFS-SchemaMajorVersion. I found it on my 2008 namespaces but not on my 2000. I looked it up.

The article explains that this attribute was "Implemented on Windows Server® 2008 operating system and Windows Server® 2008 R2 operating system." Which means it's not present on Windows Server 2000 namespaces!

So, what can I do with this information? Can I check if the attribute is present on the namespace and make an assumption that it is either 2000 or 2008, depending on if it's there or not? Yep!

Now to the fun part. 

This VBScript will bind to the DFS-Configuration and loops through all the namespaces it contains. It will attempt to get and check for the msDFS-SchemaMajorVersion for each namespace to see if its empty or not. If it cannot retreive it, it will make the assumption that it's dealing with a namespace in WS 2000 mode. On the other hand, if it can retreive it, we're dealing with a 2008 namespace. Right? Well, I and my script will assume so. At least until the next Windows Server operating system is released. (Then perhaps we can check for the version number instead?).

If InStr(1, WScript.FullName, "wscript.exe", vbTextCompare) Then
  Set objShell = WScript.CreateObject("WScript.Shell")
 
objShell.Run "cmd /k cscript.exe """ & WScript.ScriptFullName & ""
  WScript.Quit
End If

Set objRootDSE = GetObject("LDAP://RootDSE")
Set objSysInfo = CreateObject("ADSystemInfo")

Const MODE_DFS2000 = "(Windows Server 2000 mode)"
Const MODE_DFS2008 = "(Windows Server 2008 mode)"
Const MODE_ERROR  = "(Could not determine mode)"

strDomainComponents = objRootDSE.Get("DefaultNamingContext")
strDomainFQDN = objSysInfo.DomainDNSName

Set objDFS = GetObject("LDAP://CN=Dfs-Configuration,CN=System," & strDomainComponents & "")
For Each dfsNameSpace In objDFS
  On Error Resume Next
    If IsEmpty(dfsNameSpace.Get("msDFS-SchemaMajorVersion")) Then
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_DFS2000
    ElseIf Not IsEmpty(dfsNameSpace.Get("msDFS-SchemaMajorVersion")) Then
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_DFS2008
    Else
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_ERROR
    End If
  On Error Goto 0
Next