Simple Augmented Browsing for Website Development and Troubleshooting

Often times developers face the challenge of quickly making a few trivial changes to an existing website just to see how a change of an image or a css style would look. We can make these changes in a development environment, no problem there. But what if you have to do it to a live website, and the changes cannot impact any other user except yourself?

Augmented browsing techniques can come to our rescue. You might have used GreaseMonkey, a popular add-on that lets you change the look and feel of any website. In short, it installs scripts that read the DOM of the loaded html and alter its html/css etc. But creating and running the scripts might be overkill or cumbersome to work with, especially if you need to test with many different browsers.

Let’s take an alternative approach. How about intercepting the incoming resource file requested by the webpage and loading a different resource file that is stored in your local drive? aha!

For this I use my favorite tool, Fiddler. It is a debugging proxy that sits between your browser and the server and intercepts calls between them. The tool has many features that make a developer’s life easier, and we are going to use the feature “AutoResponder”.

clip_image002

Here are the steps to follow to intercept an image file and point to your own image.

a. Download, install & run Fiddler

b. Select the AutoResponder tab, check ‘Enable automatic responses’, and check ‘Unmatched requests pass-through’. This says that if no rule matches the incoming resource then do not intercept and use the file served from the web server.

c. Get the url of the image on the page you want to change. You can probably find it by viewing the page’s source code.

d. Have your replacement image in your local drive ready.

e. Click Add Rule button (or you can import the rule, if you previously exported it).

f. In the bottom of the window, type in the relative URL of the source image in the first dropdown.

clip_image004

g. For the second one, type in the local file path of the image file to be used in place of the original one.

h. Save

i. Refresh the webpage, voila! new image in place of the original!

clip_image006

j. You may turn on/off the interception by check/uncheck the checkbox in front of each of the rules you specify

clip_image008

To alter a .css or .js file, first download the file from the web server and store it in your local drive, add the interception rule, do modifications to that local file and refresh the page to see the change.

Happy coding!

Adding All Services to an Existing Office 365 User License

When working with our clients, we often find that they have enabled only some of the services within an Office 365 license.  Some companies, for example, may enable E3 licenses for a subset of users, but they don’t enable Lync Online.  While it’s very easy to add a service from within the Office 365 Admin Center, this method is not very efficient when a company has to modify several hundred or thousands of accounts and instead want to leverage Windows PowerShell.

By combining the use of the New-MsolLicenseOptions and Set-MsolUserLicense cmdlets, it’s possible to remove and add services.  In the following example, the account has been assigned all E3 services except for Office 365 ProPlus (OFFICESUBSCRIPTION) and Lync Online ‎(Plan 2) (MCOSTANDARD):

clip_image001

The company wants to add the Office 365 ProPlus service, but keep the Lync Online service disabled.  Running the following cmdlet will set the disabled service to only “MCOSTANDARD”:

$LicenseOptions = New-MsolLicenseOptions -AccountSkuId "company:ENTERPRISEPACK" -DisabledPlans MCOSTANDARD

Running this next cmdlet will change the license settings:

Get-MsolUser -UserPrincipalName john.doe@company.com | Set-MsolUserLicense -LicenseOptions $LicenseOptions

Since the “OFFICESUBSCRIPTION” service was not explicitly excluded in the “DisabledPlans” parameter, by default, it will now be enabled:

clip_image002

Note that the “ProvisioningStatus” for OFFICESUBSCRIPTION changed from “Disabled” to “PendingInput”.  When viewing the license settings in the Admin Center, the service will now be enabled under the E3 license details:

clip_image004

Now, again consider the scenario where a company has assigned E3 licenses, but left the Office 365 ProPlus and Lync Online (Plan 2) services disabled for all E3 licensed users.  The company now wants to enable all services, and not exclude any services.  In the past, Microsoft support has always provided that the only way to accomplish this is to remove the license, then reassign it without any “LicenseOptions”, effectively enabling all services.  While this method is perfectly safe, some companies are a bit apprehensive to make this change to a large number of accounts at once, for fear of disconnecting the users’ mailboxes and causing a service outage.

Instead of removing and re-adding the license, it’s possible to accomplish the same task by setting the “DisabledPlans” parameter to “$Null” within the “New-MsolLicenseOptions” cmdlet.  Example:

$LicenseOptions = New-MsolLicenseOptions -AccountSkuId "company:ENTERPRISEPACK" -DisabledPlans $Null
Get-MsolUser -UserPrincipalName john.doe@company.com | Set-MsolUserLicense -LicenseOptions $LicenseOptions

Note that both the OFFICESUBSCRIPTION and the MCOSTANDARD “ProvisioningStatus” have changed to “PendingInput”, and the services will show as enabled under the E3 license details in the Admin Center:

clip_image005

clip_image007

I hope you find this tip useful when managing your Office 365 licenses with Windows PowerShell.

Barry Thompson
Principal Consultant

Powershell – Get started with the low-hanging fruit

Much has been written about PowerShell (PS) cmdlets, whether they be the new ones available in v3, the Quest tools, those built into various products like SQL Server and Citrix XenDesktop, or the PSCX community extensions (to name just a few). These cmdlets are great, but don’t forget that PS is an object-oriented, interpreted scripting language, capable of taking advantage of .NET APIs and COM objects like IE and MS Word. Need to write a utility with a graphical interface so the help desk can easily provision a service for a user? Use the .NET System.Drawing and System.Windows.Forms namespaces. Want to open a web page repeatedly to ‘screen scrape’ it? Create a new object of type ‘InternetExplorer.Application’ and have at it!

Admittedly, there is a learning curve involved here, but here are some PS examples that allow you to use PowerShell quickly you may have overlooked:

1. Use the get-process cmdlet when tracking down the process that is causing performance problems. Combine that with the out-gridview and you can add additional selection criteria, and then select just the processes you want to output:

Get-process | out-gridview –passthru | fl

1a  1b 1c

2. The out-gridview is fine if you have a small amount of data, but you need more automation if you have a large base. Combining the built-in csvde.exe with import-csv lets you scale to larger sizes:

Csvde –f users.csv

2a

$users = import-csv users.csv

$users | where {$_.sAMAccountName.StartsWith(“S”)} | select name

2b

3. Let’s gather CPU, memory, and disk statistics. Let’s write a PS workflow (PSFW, new with v3) that will gather all of the statistics in parallel, then pass it an array of the performance counters provided by perfmon we want to examine:

Workflow gather_stats ($perfmoncounters)
{
    Foreach –parallel ($counter in $perfmoncounters)
   {
      (Get-counter $counter). CounterSamples
    }
}
$arr_perfmoncounters = ‘\Processor(_Total)\% Processor Time’, `
      ’\Memory\Available Bytes’, `
      ‘\PhysicalDisk(_Total)\% Disk Time’

3a

4. MS Excel is a powerful tool. Can PS export data into a format Excel can use? Yes it can! This example uses the Quest ActiveRoles Management Shell for Active Directory (ARMS), but you could also use get-user from the Exchange PS management tools, etc., then export the results to a comma separated value (CSV) file that Excel can import:

Get-QADUser | Export-Csv -NoTypeInformation -Encoding OEM users.csv

4a

MS Excel can open the resulting file, and you can sort, filter, etc., as you normally would inside Excel.

5. All of the examples I have given show how to read data, but what if you want to write data back into AD? No problem! Just combine the earlier examples to gather your data, then send the results into the built-in MS tool dsmod.exe:

5a

And, since I am encouraging you to mix-and-match tools, what if you want to use the Quest ARMS commands and the Exchange cmdlets (for example) at the same time? Here’s a command that will load all of the PS modules you have installed on your system into a single PS session. Open up a PS window and use this command, courtesy of the Microsoft Scripting Guys:

Get-Module -ListAvailable | Import-Module

6a

Just a caveat for those of you running Exchange 2007 or 2010: These products have not been updated to use v3 of PowerShell yet, so hold off installing it until MS says it is safe.

And for those of you who looked at remoting in v2: PS v2 introduced remoting, but v3 makes the whole thing more robust and reliable. If v2 remoting left you with a bad taste, give it a fresh try with v3 – I guarantee you will have a better experience!

John Scaggs, MCT, MCITP

Senior Consultant, Advanced Infrastructure Group

Confirming Basic Domain Controller Connectivity Through VBScript

On a recent domain migration project, the customer requested that the automated migration process being created would included a check for access to a domain controller for the current domain and for the new domain. In this customer’s environment, the vast majority of clients were Windows XP Professional machines without PowerShell deployed. Thus, the customer wanted all of the scripting done in either VBScript or basic CMD batch processing.  To meet this particular request, I used VBScript. Although there’s not anything magical about the result, I found that locating some of the details was tricky. This post describes the final script and some tips about getting to the final result.

Script Header

The script starts with a basic header:

' CheckDCConnectivity.vbs
' Confirms current connectivity to domain controllers for the legacy
' domain and new domain using "ping".
' Michael C. Bazarewsky, Bennett Adelson
' Version 2.0

' Returns:
' .... 0 for connectivity to both found
' .... 40000 for unable to get to legacy
' .... 40001 for unable to get to the new domain
' .... n for Err.Number value n

This header is basic comments and intro information. Nothing fancy. The error code values are up out of the way of the system error range and below the COM/OLE error range; the numbers are somewhat arbitrary but this ensures I am not stepping on errors Microsoft code is returning. For some other scripts I use the standard error codes (e. g. System Error Codes) where it makes sense. Here I did not because I wanted different error codes for the two specific domains and the generic codes don’t let me do that.

Moving on, we set variable declaration to be required and catch errors on our own. Later there will be code that shows the error catches; this allows us to have more control over error paths. I’m following best practice with respect to the variable declaration requirement as it helps catch typos and the like:

Option Explicit 
On Error Resume Next

The next line is a subroutine call:

ForceTo32BitHost

The subroutine is as follows:

' Forces the script to run in the 32-bit host on a 64-bit machine
' cf. http://blogs.msdn.com/b/joshpoley/archive/2008/09/18/running-32bit-dependent-scripts-in-a-64bit-world.aspx
Sub ForceTo32BitHost
    Dim objShell, strCPU, strHost
    Dim strSysWOW64Host, strNewCommand, strArgument
    Dim objExec, intReturnCode
    
    Set objShell = CreateObject("WScript.Shell")
    strCPU = LCase(objShell.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%"))
    strHost = LCase(WScript.FullName)
    
    If ((InStr(strHost, "system32") <> -1) And (strCPU = "amd64")) Then
        strSysWOW64Host = Replace(strHost, "system32", "syswow64") 
        strNewCommand = strSysWOW64Host + " //NoLogo """ & WScript.ScriptFullName + """"
        For Each strArgument in WScript.Arguments
            strNewCommand = strNewCommand + " """ + strArgument + """"
        Next
        WScript.Echo "Calling self with 32-bit host"
       
        ' Run the child script
        Set objExec = objShell.Exec(strNewCommand)       : CheckForError "Launching 32-bit script host with this script"
        
        Do While (objExec.Status = 0)
            If (Not objExec.StdOut.AtEndOfStream) Then
                WScript.Echo objExec.StdOut.ReadAll
            End If
            If (Not objExec.StdErr.AtEndOfStream) Then
                WScript.Echo objExec.StdErr.ReadAll
            End If
            WScript.Sleep 100
        Loop 
        
        ' Check the child results   
        intReturnCode = objExec.ExitCode
        If (intReturnCode <> 0) Then
            FailWithMessage intReturnCode, "Recursive call script failure " & intReturnCode & " from " & strNewCommand & vbCrLf
        End If
        WScript.Quit 0
    End If
End Sub

Now why am I doing this? The later code uses a 32-bit COM object, which on an x64 machine means we need to make sure we are in the 32-bit script host. If we aren’t, we can’t get to the COM object. You can try, but you will get an error 429, "ActiveX component can't create object" if you do. The blog post mentioned in the comment to the subroutine gives the initial reference source for the code, although that post uses JScript instead of VBScript and ignores StdErr. This subroutine shows some of the error handling calls as well, which I’m going to skip over explaining for now and come back to.

Next are the variable declarations:

Dim objSystemInfo, strCurrentDomain 
Dim objIADSTools 

Dim strCurrentDC, strNewDC, intReturnValue

Then, we ask for the current domain name of the system. I have seen more complicated versions of this code that use WMI to get to the Win32_ComputerSystem object (and in fact had that for a while in another script for the same project), but this is a lot shorter and works from Windows 2000 forward:

' What domain is the machine currently in?
Set objSystemInfo = CreateObject("ADSystemInfo")                                            : CheckForError "Creating object ADSystemInfo"
strCurrentDomain = objSystemInfo.DomainDNSName                                              : CheckForError "Looking up machine's current DNS domain name"
WScript.Echo "Current domain: " & strCurrentDomain

Again I’m going to skip the error handling for now. We need to get the domain controller name next; that’s where the 32-bit COM object comes in: IAdsTools.dll. This file initially was in the Windows Support Tools; the most recent version available that I could find is 1.1.0.2234 at the Microsoft download site (the Windows Support Tools for Windows Server 2003 Service Pack 2 – just download the CAB and pull the file out using Windows Explorer). The customer deployed the migration tool package using an MSI and their internal deployment tool so registering a COM component was not a big deal.

Anyway, once we have that available, we can then ask it to look up DCs through the standard OS mechanism:

' What DC will the OS use for the current domain?
Set objIADSTools = CreateObject("IADsTools.DCFunctions")                                    : CheckForError "Creating object IADsTools.DCFunctions"
objIADSTools.SetDsGetDcNameFlags = "DS_FORCE_REDISCOVERY,DS_IS_DNS_NAME,DS_RETURN_DNS_NAME" : CheckForError "Setting DS Get DC Name Flags"
intReturnValue = objIADSTools.DsGetDcName(CStr(strCurrentDomain), "", 1)                    : CheckForError "Looking up current domain controller for current domain"
If (intReturnValue <> 0) Then
    FailWithMessage intReturnValue, "Looking up current domain controller for current domain"
End If
strCurrentDC = objIADSTools.DCName                                                          : CheckForError "Getting the located DC name for current domain"
WScript.Echo "DC for current domain: " & strCurrentDC

This is where a couple of things come up that caused me substantial pain that I hope I can save you. Specifically, there were two pieces:

  1. The documentation for SetDsGetDcNameFlags is at best misleading. It took me looking into the COM object with the OleView tool form the Windows SDK to figure out that SetDsGetDcNameFlags is actually a property, not a method, on the DCFunctions interface. The Windows SDK can be installed from the Microsoft download site; I haven’t linked to it because it gets updated fairly often and I don’t want to have a stale link.
  2. The "everything is a Variant" nature of VBScript caused me pain with DsGetDcName, hence the explicit string conversion.

Now that the script has the DC name, it can attempt to ping it:

' Can we ping it? 
If Not Ping(strCurrentDC) Then 
    FailWithMessage 40000, "Cannot ping current DC"
End If 

What is the Ping subroutine? Glad you asked:

' This function returns True if the specified host could be pinged.
' strHostName can be a computer name or IP address.
' The Win32_PingStatus class used in this function requires Windows XP or later.
' This function is based on the TestPing function in a sample script by Don Jones
' http://www.scriptinganswers.com/vault/computer%20management/default.asp#activedirectoryquickworkstationinventorytxt
' credit: http://www.robvanderwoude.com/vbstech_network_ping.php
Function Ping(strHostName)

    ' Standard housekeeping
    Dim colPingResults, objPingResult, strQuery

    ' Define the WMI query
    strQuery = "SELECT * FROM Win32_PingStatus WHERE Address = '" & strHostName & "'"

    ' Run the WMI query
    Set colPingResults = GetObject("winmgmts:root\cimv2").ExecQuery(strQuery)

    ' Translate the query results to either True or False
    For Each objPingResult In colPingResults
        If Not IsObject(objPingResult) Then
            Ping = False
        Else
            If objPingResult.StatusCode = 0 Then
                Ping = True
            Else
                Ping = False
            End If
            WScript.Echo "Ping status code for " & strHostName & ": " & _
                objPingResult.StatusCode
        End If
    Next

    Set colPingResults = Nothing
End Function

A lot of code, but the short explanation is that the script is using the ICMP ping functionality added to WMI in Windows XP/Server 2003 to ping the remote host.

Moving on further, it’s the same work for the new domain instead of the legacy domain:

' What DC will the OS use for the new domain?
intReturnValue = objIADSTools.DsGetDcName("NewDomain.name")                                   : CheckForError "Looking up current domain controller for NewDomain.name"
If (intReturnValue <> 0) Then
    FailWithMessage intReturnValue, "Looking up current domain controller for NewDomain.name"
End If
strNewDC = objIADSTools.DCName                                                              : CheckForError "Getting the located DC name for NewDomain.name"
WScript.Echo "DC for NewDomain.name: " & strNewDC

' Can we ping it?
If Not Ping(strNewDC) Then
    FailWithMessage 40001, "Cannot ping NewDomain.name DC"
End If

WScript.Echo "Connectivity appears valid, returning with exit code 0"
WScript.Quit 0

Yes, this code is slightly redundant – I should probably have a lookup/ping subroutine instead of clipboard inheritence. And, the new domain name should be in a constant rather than repeated in several places to make it easier to change. Feel free to do a better job!

So that’s the main part of the script. All that’s left is the error handling. This is not the prettiest code but it meets the need here:

' Check to see if we have an outstanding error condition
Sub CheckForError(strLocation)
    If Err.Number <> 0 Then
        WScript.Echo "UNEXPECTED ERROR INTERCEPTED: " & strLocation
        FailWithMessage Err.Number, "Description: " & Err.Description
    End If
End Sub

' Fail with the given exit code and message
Sub FailWithMessage(intExitCode, strMessage)
    WScript.Echo intExitCode & ": " & strMessage
    WScript.Quit intExitCode
End Sub

The complete script is below:

' CheckDCConnectivity.vbs
' Confirms current connectivity to domain controllers for the legacy
' domain and new domain using "ping".
' Michael C. Bazarewsky, Bennett Adelson
' Version 2.0

' Returns:
' .... 0 for connectivity to both found
' .... 40000 for unable to get to legacy
' .... 40001 for unable to get to the new domain
' .... n for Err.Number value n

Option Explicit
On Error Resume Next

ForceTo32BitHost

Dim objSystemInfo, strCurrentDomain
Dim objIADSTools
Dim strCurrentDC, strNewDC, intReturnValue

' What domain is the machine currently in?
Set objSystemInfo = CreateObject("ADSystemInfo")                                            : CheckForError "Creating object ADSystemInfo"
strCurrentDomain = objSystemInfo.DomainDNSName                                              : CheckForError "Looking up machine's current DNS domain name"
WScript.Echo "Current domain: " & strCurrentDomain

' What DC will the OS use for the current domain?
Set objIADSTools = CreateObject("IADsTools.DCFunctions")                                    : CheckForError "Creating object IADsTools.DCFunctions"
objIADSTools.SetDsGetDcNameFlags = "DS_FORCE_REDISCOVERY,DS_IS_DNS_NAME,DS_RETURN_DNS_NAME" : CheckForError "Setting DS Get DC Name Flags"
intReturnValue = objIADSTools.DsGetDcName(CStr(strCurrentDomain), "", 1)                    : CheckForError "Looking up current domain controller for current domain"
If (intReturnValue <> 0) Then
    FailWithMessage intReturnValue, "Looking up current domain controller for current domain"
End If
strCurrentDC = objIADSTools.DCName                                                          : CheckForError "Getting the located DC name for current domain"
WScript.Echo "DC for current domain: " & strCurrentDC

' Can we ping it?
If Not Ping(strCurrentDC) Then
    FailWithMessage 40000, "Cannot ping current DC"
End If

' What DC will the OS use for the new domain?
intReturnValue = objIADSTools.DsGetDcName("NewDomain.Name")                                   : CheckForError "Looking up current domain controller for NewDomain.Name"
If (intReturnValue <> 0) Then
    FailWithMessage intReturnValue, "Looking up current domain controller for NewDomain.Name"
End If
strNewDC = objIADSTools.DCName                                                              : CheckForError "Getting the located DC name for NewDomain.Name"
WScript.Echo "DC for Newdomain.Name: " & strNewDC

' Can we ping it?
If Not Ping(strNewDC) Then
    FailWithMessage 40001, "Cannot ping NewDomain.Name DC"
End If

WScript.Echo "Connectivity appears valid, returning with exit code 0"
WScript.Quit 0

' Check to see if we have an outstanding error condition
Sub CheckForError(strLocation)
    If Err.Number <> 0 Then
        WScript.Echo "UNEXPECTED ERROR INTERCEPTED: " & strLocation
        FailWithMessage Err.Number, "Description: " & Err.Description
    End If
End Sub

' Fail with the given exit code and message
Sub FailWithMessage(intExitCode, strMessage)
    WScript.Echo intExitCode & ": " & strMessage
    WScript.Quit intExitCode
End Sub

' This function returns True if the specified host could be pinged.
' strHostName can be a computer name or IP address.
' The Win32_PingStatus class used in this function requires Windows XP or later.
' This function is based on the TestPing function in a sample script by Don Jones
' http://www.scriptinganswers.com/vault/computer%20management/default.asp#activedirectoryquickworkstationinventorytxt
' credit: http://www.robvanderwoude.com/vbstech_network_ping.php
Function Ping(strHostName)

    ' Standard housekeeping
    Dim colPingResults, objPingResult, strQuery

    ' Define the WMI query
    strQuery = "SELECT * FROM Win32_PingStatus WHERE Address = '" & strHostName & "'"

    ' Run the WMI query
    Set colPingResults = GetObject("winmgmts:root\cimv2").ExecQuery(strQuery)

    ' Translate the query results to either True or False
    For Each objPingResult In colPingResults
        If Not IsObject(objPingResult) Then
            Ping = False
        Else
            If objPingResult.StatusCode = 0 Then
                Ping = True
            Else
                Ping = False
            End If
            WScript.Echo "Ping status code for " & strHostName & ": " & _
                objPingResult.StatusCode
        End If
    Next

    Set colPingResults = Nothing
End Function

' Forces the script to run in the 32-bit host on a 64-bit machine
' cf. http://blogs.msdn.com/b/joshpoley/archive/2008/09/18/running-32bit-dependent-scripts-in-a-64bit-world.aspx
Sub ForceTo32BitHost
    Dim objShell, strCPU, strHost
    Dim strSysWOW64Host, strNewCommand, strArgument
    Dim objExec, intReturnCode
    
    Set objShell = CreateObject("WScript.Shell")
    strCPU = LCase(objShell.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%"))
    strHost = LCase(WScript.FullName)
    
    If ((InStr(strHost, "system32") <> -1) And (strCPU = "amd64")) Then
        strSysWOW64Host = Replace(strHost, "system32", "syswow64") 
        strNewCommand = strSysWOW64Host + " //NoLogo """ & WScript.ScriptFullName + """"
        For Each strArgument in WScript.Arguments
            strNewCommand = strNewCommand + " """ + strArgument + """"
        Next
        WScript.Echo "Calling self with 32-bit host"
       
        ' Run the child script
        Set objExec = objShell.Exec(strNewCommand)       : CheckForError "Launching 32-bit script host with this script"
        
        Do While (objExec.Status = 0)
            If (Not objExec.StdOut.AtEndOfStream) Then
                WScript.Echo objExec.StdOut.ReadAll
            End If
            If (Not objExec.StdErr.AtEndOfStream) Then
                WScript.Echo objExec.StdErr.ReadAll
            End If
            WScript.Sleep 100
        Loop 
        
        ' Check the child results   
        intReturnCode = objExec.ExitCode
        If (intReturnCode <> 0) Then
            FailWithMessage intReturnCode, "Recursive call script failure " & intReturnCode & " from " & strNewCommand & vbCrLf
        End If
        WScript.Quit 0
    End If
End Sub

I hope this helps you with your migration efforts or just with general script tasks!

– Michael C. Bazarewsky