Windows 8 Consumer Preview Available!

The Consumer Preview of Windows 8 is available for download. This release is the first official Windows 8 build drop from Microsoft since the Developer Preview, which was released last September. This was downloaded quite rapidly, surpassing the 500,000 downloads mark within 24 hours of release, and the 3 million mark by December, so I would expect this to surpass that easily.

If you are like me and ready to jump on the download follow the links below. I was able to download at a pretty good speed so I know Microsoft is ready for this Smile

[Official Links from Microsoft]

Download Windows 8 Consumer Preview (64-bit)
Sha 1 hash — 1288519C5035BCAC83CBFA23A33038CCF5522749

Download Windows 8 Consumer Preview (32-bit)
Sha 1 hash — E91ED665B01A46F4344C36D9D88C8BF78E9A1B39

You can follow some live feeds from the MWC here: http://live.theverge.com/Event/Microsofts_Windows_8_Consumer_Preview_event_at_MWC_2012

Jason Condo
Principal Consultant – Systems Management

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

Windows Intune Webcast

How Windows Intune Benefits Any Size Company

Presented By: David Norling-Christensen – Bennett Adelson

Thursday February 23rd 2012 @ 1:00pm

Windows Intune is Microsoft’s Cloud based solution to PC management and security.  Come find out what Windows Intune really is and how it can help companies of all sizes.  The session will explore Windows Intune along with the daily challenges the product solves and those challenges that must be overcome to utilize Windows Intune.

Register here to receive the webcast information: http://www.bennettadelson.com/EventDetails.aspx?ID=75020cd6-114c-e111-aef3-1cc1def177b7

FIM 2010 – User RCDC to Add User to Groups

In FIM 2010, there is no way to use the current RCDC (Resource Control Display Configuration) to create a “user” interface, not group interface, that will allow a user to be added to a group. The current out-of-the-box interface for FIM only allows for group interfaces to be used for adding users to specific groups. In the world of self service, administrators should be moving away from group and user management. However, with FIM 2010 the interface is built for a more technical staff, which is a problem for the everyday user. Below is how I solved this problem in FIM 2010. The below explaination is technical and FIM 2010, Visual Basic, and XML knowledge is needed.

How did I use the “user” interface and achieve user addition or removal from groups?

  1. Changes to the schema
  2. Changes to the User RCDC
  3. Created a Custom WorkFlow in VB.NET

Step 1: Changes to the schema

I created a new resource type reference object which contains the names of the groups I wanted users to request or to be able to be added to.  This gave me a short, easy custom list for any admin to add a role/groups to without needing to search. I then created a reference attribute on the user resource type to view the new reference type groups in a drop down. (Attributes are added in the Schema Section in the FIM Portal).

Step 2: Changes to the User RCDC

To use this drop down in the RCDC for users, I added this to the User Edit RCDC:

<my:Control my:Name="GroupRoleName" my:TypeName="UocDropDownList" my:Caption="Access Role" my:Description="Access Role"  my:RightsLevel="{Binding Source=rights, Path=Group_Ref}" my:AutoPostback="true">
    <my:Properties>
        <my:Property my:Name="Required" my:Value="{Binding Source=schema, Path=Group_Ref.Required}"/>
        <my:Property my:Name="Columns" my:Value="40"/>
        <my:Property my:Name="HintPath" my:Value="Hint"/>
        <!--The new user attribute which is a reference attribute-->
        <my:Property my:Name="ItemSource" my:Value="{Binding Source=search, Path=Group_Role_Ref}"/>
        <!--The new resource type attribute where my list of roles are located--> 
        <my:Property my:Name="SelectedValue" my:Value="{Binding Source=object, Path=Group_Ref, Mode=TwoWay}"/>
    </my:Properties>
</my:Control>

FYI, the reference attribute will only be stored as an ObjectID; I look up the display name in my workflow below.

Then, I created sets that would kick off an add or remove Management Policy Rule (MPR).  I used a custom boolean attribute that if selected would add/remove the user to the set, and then kick off an MPR, and finally run a WorkFlow activity.

Step 3: Created a Custom WorkFlow in VB.NET

Finally, I created a new custom WorkFlow where I used the new resource type display names for quering the groups with a dynamic XPath query in a EnumerateResourceActivity and then an UpdateResourceActivity which added/removed the user from a group.

Here is the important code when running an enumeration activity:

Enumeration Code

Make sure to add this above the Private Sub InitializeComponent():

Private codeActivity1 As CodeActivity

Make sure to add this in the Private Sub InitializeComponent() above the Me.CanModifyActivities = False:

Me.Name = "GroupActivity"
Me.codeActivity1 = New System.Workflow.Activities.CodeActivity()
Me.codeActivity1.Name = "codeActivity1"
AddHandler Me.codeActivity1.ExecuteCode, AddressOf Me.GetGroupEnum_ExecuteCode 'CodeActivity is the child of my enumerationresourceactivity Me.enumerateResourcesActivity1.Activities.Add(Me.codeActivity1)
Me.CanModifyActivities = False

Your activity design document should look like this afterwards for the enumerate activity or it will return a count but all other data will be NULL.

Now for some of the code for setting looking up the of the display name of the Resource Type using readresourceactivity:

Private Sub UpdateGroup_ExecuteCode(sender As System.Object, e As System.EventArgs)
    Me.Log("Starting Next Code ")
    Dim currentRequest As RequestType = currentRequestInformation_CurrentRequest1
    Me.Log("After Request")
    Dim RoleQuery As String = Me.Target_Resource1(Me.GUIinsert) 'GUI Insert Taken from the WorkFlow setup/GUI screen
    Me.Log("Role Name Returned1Ref1::" & RoleQuery & ". ")
    Me.GetDisplayName.ActorId = New System.Guid("00000000-0000-0000-0000-000000000000")
    Me.GetDisplayName.Name = "GetDisplayName"
    Me.GetDisplayName.Resource = Nothing
    Me.GetDisplayName.ResourceId = New System.Guid(RoleQuery)
    Me.GetDisplayName.SelectionAttributes = New String() {"DisplayName"}
End Sub

Enumeration Activity Code

Private Sub GetDisplay_ExecuteCode(sender As System.Object, e As System.EventArgs)
    Dim RoleQuery2 As String = Me.GetDisplayName_Resource1("DisplayName")
    Me.Log("Role Name Returned1Ref2:" & RoleQuery2 & ". ")
    Me.enumerateResourcesActivity1.ActorId = New System.Guid("00000000-0000-0000-0000-000000000000")
    Me.GetGroup.Name = "GetGroup"
    Me.enumerateResourcesActivity1.PageSize = 100
    Me.enumerateResourcesActivity1.Selection = New String() {"AccountName"}
    Me.enumerateResourcesActivity1.SortingAttributes = Nothing Me.enumerateResourcesActivity1.TotalResultsCount = 0 Me.enumerateResourcesActivity1.XPathFilter = "/Group[contains(" & Me.SearchScope & ",'" & RoleQuery2 & "')]" 'SearchScope is taken from WorkFlow GUI screen
    Me.Log("XPATH Name Returned1Ref2:" & Me.enumerateResourcesActivity1.XPathFilter & ". ")
End Sub

Enumeration and Update Code

Private Sub GetGroupEnum_ExecuteCode(sender As System.Object, e As System.EventArgs)
    Dim QueryGr As Guid = Nothing Me.Log("Role Name Returned1Ref3:. ")
    If Me.enumerateResourcesActivity1.TotalResultsCount > 0 Then
        Me.Log("Role Name Returned1Ref3:Inside. ")
        Me.Log("Role Name Returned1Ref4:seq. ")
        Dim currentItem As ResourceType = TryCast(EnumerateResourcesActivity.GetCurrentIterationItem(DirectCast(sender, CodeActivity)), ResourceType)
        If (currentItem Is Nothing) Then
            Me.Log("Role Name Returned1Ref5:Null.")
            Exit Sub
        Else
            Me.Log("Role Name Returned1Ref5:Not Null.")
            Try
                Me.Log("Number : " & Convert.ToString(Me.enumerateResourcesActivity1.TotalResultsCount))
                Me.Log("UID : " & currentItem.ObjectID.GetGuid().ToString())
            Catch ex As Exception
            End Try
            ReturnValue = currentItem.ObjectID.GetGuid().ToString()
        End If
    End If
    QueryGr = New Guid(ReturnValue)
    Dim GroupValue As Guid = Me.Target_ResourceId1
    Dim updateInstruction2 As Microsoft.ResourceManagement.WebServices.WSResourceManagement.UpdateRequestParameter = New Microsoft.ResourceManagement.WebServices.WSResourceManagement.UpdateRequestParameter() updateInstruction2.PropertyName = "ExplicitMember"
    If Me.AddRemoveGroup = "Add" Then 'Taken from WorkFlow GUI Screen if the workflow is remove or add
        updateInstruction2.Mode = UpdateMode.Insert
    ElseIf Me.AddRemoveGroup = "Remove" Then
        updateInstruction2.Mode = UpdateMode.Remove
    End If
    Try
        Me.update.UpdateParameters = New UpdateRequestParameter() {updateInstruction2}
        Me.update.ActorId = New System.Guid("00000000-0000-0000-0000-000000000000")
        Me.update.ResourceId = QueryGr
    Catch ex As Exception
        Me.Log("Role Name Returned16:" & ex.ToString() & ". ")
    End Try
End Sub
GUI Screen:
I hope this help others with an example of how to customize the “user” RCDC screen.

Thanks,
Identity Management and Active Directory Principal Engineer
Nathan Mertz | Bennett Adelson | Columbus

The User-Centric Approach and ConfigMgr 2012

This week I presented at the Northeast Ohio System center Users Group on deploying Windows 7 with Configuration Manager 2012. While there are many things different, the great thing is that if your are familiar with OSD in 2007, you can be deploying in 2012 pretty quickly. You can even port your task sequences over.

However, I think the better discussion of the night was the implications the new approach to user-centric deployment over system-centric deployment that we have had in the past. I came up with an “equation” to help explain how identifying the different aspects of the user experience will help you as an admin provide the most flexibility to your enterprise and leverage all the cool features that ConfigMgr has to offer now to allow you to be agile and empower the end–user.

image

One of the questions that came up is wouldn’t this idea of empowerment and user self service put an undue burden on the end-user. I say quite the contrary. In our connected society, we are trained more and more to know how to find what we want and download it for consumption. We do this with our media (YouTube, Netflix, Pandora), our software (when was the last time you went to a store to buy software), our mobile devices (application markets for Android, iPhone, Microsoft, and Blackberry), and the constant barrage of hashes, tags, and links to go look up more info.

Providing our users with this same functionality (with approval processes and controls of course) through ConfigMgr enables them to get what they want, when they want, making it simple and less obtrusive to their day to day functions of being productive and enabling you to focus on the important stuff instead of manually adding users/machines to collections for deployment.

I will be explaining this in more detail in subsequent posts, but for the time being here is my equation and a link to the NEO System Center User Group site where you can download the slide deck from the presentation.

http://myitforum.com/myitforumwp/groups/cleveland-ohio-system-center-user-group

http://www.bennettadelson.com

Jason Condo

Enabling Users for Office 365 Licensing Made Easy

When transitioning to the cloud, some of Microsoft’s new PowerShell commands can be hard to find solid answers on and are completely different in some cases from what most Exchange administrators are used to using.  The Set-Msoluser cmdlet for instance is not something that you had ever seen in Exchange 2007 or 2010.  I’m often asked the easiest method for giving a user a license without manually clicking through the Portal for 1000+ users.  Today I’ll share a simple way to get this done and save time during a migration.

These new PowerShell cmdlets make assigning licenses to users very simple and much faster than using the Online Services portal to manually assign licenses. This post will walk you through how to make a basic PowerShell script that reads a CSV file to activate and assign licenses to users in your target Office 365 environment.

Step 1 – Determine the license types you have.

Before we configure the script we’ll need to know what the license types are in order to include them in our script. 

Connect to the Msol service using Connect-MsolService and enter in valid administrator credentials.

Run the following PowerShell command to determine the license types:

Get-MsolAccountSku | Format-Table AccountSkuId, SkuPartNumber

The output should look something like this:


AccountSkuId SkuPartNumber
------------ -------------
COMPANY:STANDARDPACK STANDARDPACK
COMPANY:ENTERPRISEPACK ENTERPRISEPACK

I’ve removed the company name before the : but it should be similar. For this example’s purposes let’s call the licenses TEST:STANDARDPACK and TEST:ENTERPRISEPACK.

Step 2 – Configure your CSV input file

Now we can start to create the input file.  We’ll need a bit more information about your users but generally we’ll set the CSV up with 3 columns:  Username, LicenseType, Location.  The Username column will be the UPN or online login name of the user (these should match).  The LicenseType will be based upon the outputs we received from step 1.  The Location is where the location needs to be set for each user, such as >US for United States, IN for India, MX for Mexico, and so on.  It should look something like the following.

username licensetype location
user01@test.onmicrosoft.com TEST:STANDARD us
user02@test.onmicrosoft.com TEST:ENTERPRISE in
user03@test.onmicrosoft.com TEST:ENTERPRISE mx
user04@test.onmicrosoft.com TEST:STANDARD us

Save the inputfile in ‘msol_activate.csv’. Now we can go on the script.

Step 3 – Configure the Script

Now that our input file has been setup we can write the script accordingly.  Let’s open a notepad file.

We’ll need to define our list and just refer to it as $List and use Import-CSV to read the CSV file:

$List = Import-CSV “msol_activate.csv"

No we’ll start the loop using foreach and call out $User as each line of the $List

foreach ($User in $List)
{

In the first line we’ll set the UsageLocation since it is required before setting the license.  The name of the columns from our CSV file will be added to $User so the script can grab them specifically.

Set-MsolUser -UserPrincipalName $User.Username -UsageLocation $User.Location

Now we’ll add the license type:

Set-MsolUserLicense –userprincipalname $User.Username –addlicenses $User.LicenseType

And lastly we’ll set a default password and force the user to change that password the first time they log in, then close the loop:

Set-MsolUserPassword -UserPrincipalName $User.Username -ForceChangePassword $true -NewPassword "Office365Rules"
}

You could also add a column to your CSV file named Password and have the –NewPassword be $User.Password.

Put it all together and the script looks like this:


$List = import-csv  “msol_activate.csv"
foreach ($User in $List)
{
    Set-MsolUser -UserPrincipalName $User.Username -UsageLocation $User.Location
    Set-MsolUserLicense –UserPrincipalName $User.Username –addlicenses $User.LicenseType
    Set-MsolUserPassword -UserPrincipalName $User.Username -ForceChangePassword $true -NewPassword "Office365Rules"
}

Summary

So now we have successfully determined our license types, take our user data and put it into an input file and created a script that easily gives our users a license and a temporary password.  This script will help get your users to the cloud faster and assigned licenses much faster than manually using the portal.

 

Peter Gleek
BA Advanced Infrastructure Principal Consultant

“Previous Versions” and Shadow Copies with Very Long Paths

I was working on a server the other day and needed to recover files from a previous version of a folder through Previous Versions.  (A backup was not available on the particular folder for reasons I won’t get into here.)  I ran into a problem that I couldn’t really find documented very well anywhere and thought I would document it for others.

I first made sure there was a previous version available:

image

In fact, this was through a shadow copy, which will turn out to be very important:

image

So, we have the previous state of the folder, it’s in a local snapshot, so I could get it back, right?  Let’s see what happened.  I clicked Open on the Previous Versions tab, then navigated through Explorer to the folder that has the file I want to get.  It turns out this file is nested a few levels deep in a set of long folder names, and has a long filename as its exposed filename:

\\localhost\C$\Users\Administrator\Desktop\Somewhat Long Folder Name\Another really long folder name for a good reason that you do not know\Yet another long nested folder name believe it or not (‎Today, ‎January ‎26, ‎2012, ‏‎12 minutes ago)\Long file name here as well that will be a problem for us soon.txt

Why is the long name a problem?  Well, when I tried to copy the folder out of the shadow copy, I got this 100% correct yet not helpful error:

image

Or, in text form:

The source file name(s) are larger than is supported by the file system. Try moving to a location which has a shorter path name, or try renaming to shorter name(s) before attempting this operation.

Questionable grammar aside, the error’s suggestions, which relate to changing the source, are useless, because shadow copies are read-only.  So now what?

Well, the reason the path is too long is because with the shadow copy overhead added to the path, the filename has a length longer than MAX_PATH, or 260 characters.  I suspect Explorer still cares due to backwards compatibility, which is why the Unicode 32K path length doesn’t come into play, but that’s just a guess.  Anyway, this still leaves the problem of getting a shorter filename.

The answer is to surface or expose the shadow copy as a drive letter.  There are multiple ways to go about this.  The first one that I thought of – to use the diskshadow command that is new in Windows Server 2008 – didn’t work as I expected.  Let’s see what happened, then explain a solution.

First, we find the exact name of the shadow copy.  I listed them to a file (I used diskshadow for consistency, although vssadmin would also let me do that piece), then searched the file in Notepad:

C:\Users\Administrator\Desktop>diskshadow /l shadows.txt
Microsoft DiskShadow version 1.0
Copyright (C) 2007 Microsoft Corporation
On computer:  DEMOSERVER,  1/26/2012 11:20:09 AM

DISKSHADOW> list shadows all

… shadow listing here …

Number of shadow copies listed: 196

DISKSHADOW> exit

C:\Users\Administrator\Desktop>notepad shadows.txt

In this case I wanted the 10:41:53 AM snapshot on January 26, 2012 for the C: drive, which looked like this in the log:

* Shadow copy ID = {1cbf48de-1e49-4ae4-9a24-0c75d3dc4c6d}
– Shadow copy set: {55c21b6c-b34f-4f0c-88df-e03fc952f39e}
– Original count of shadow copies = 1
– Original volume name: \\?\Volume{12cba6d6-7540-11e0-bd41-806e6f6e6963}\ [C:\]
– Creation time: 1/26/2012 10:41:53 AM
– Shadow copy device name: \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy266
– Originating machine: DEMOSERVER
– Service machine: DEMOSERVER
– Not exposed
– Provider ID: {b5946137-7b9f-4925-af80-51abd60b20d5}
– Attributes:  No_Auto_Release Persistent Client_accessible No_Writers Differential

Next, I want to map the path to a shorter location.  I thought I could do this through diskshadow, but it turns out there’s a restriction that prevents this:

DISKSHADOW> expose {1cbf48de-1e49-4ae4-9a24-0c75d3dc4c6d} P:
Client accessible shadow copies cannot be exposed.

The GUID in the expose command is the “Shadow copy ID” given in the listing.  Because the shadow copy is accessible to the client (through Previous Versions), I couldn’t directly map it to a drive.  So now what?

Well, the trick was on a Microsoft blog — using a symbolic link to get to the shadow copy:

C:\Users\Administrator\Desktop>mklink /d c:\s \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy266\
symbolic link created for c:\s \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy266\

The link is from a very short-named local folder to the “Shadow copy device name” given in the listing.  As explained in the blog post, I didn’t forget to add a trailing slash to the mapping (it won’t work if you don’t do that).  Now, I can look at this linked version in Explorer, and copy the data!

image

Then, I deleted the symbolic link with rmdir c:\s to clean up, and that was that!

I hope this helps should you run in to the same error trying to copy from a previous version.

— Michael C. Bazarewsky