Archive for the ‘ Scripting ’ Category

How to get the number of volumes used by DPM 2010?

Just a quick hit that tells you how many volumes Microsoft Data Protection Manager uses. I got 848…

$dpmserver = $env:computername  #localhost or type your dpm server's name here
$dpmdisks = get-dpmdisk -dpmservername $dpmserver
$volumecount = 0
$dpmdisks | ForEach-Object {
  $volumecount += $_.VolumeCount
}
$volumecount

How to manage GPOs with vbScript?

You can do really really cool stuff with gpos in vbScript. I will show you how to export reports and give you some examples what else can be done going the vbScript way…
The Group Policy Management console in Windows offers you the possibility to export reports about group policy object’s settings to html files – this, for example, is an excerpt of my default domain controllers policy:

You can do this (and much more) by script too. Here is how you can do it… You can choose to save this report in a variable to do further processing in your script or you can save it to a file, just like the console does.

Function getGPOHTMLReport(strDomain, strGPOCN)
  Set objGPM = CreateObject("GPMgmt.GPM")
  Set objGPMConstants = objGPM.GetConstants()
  Set objGPMDomain = objGPM.GetDomain(strDomain, "", objGPMConstants.UseAnyDC)
  Set objGPO = objGPMDomain.GetGPO(strGPOCN)
  Set objGPMReport = objGPO.GenerateReport(objGPMConstants.ReportHTML)
  getGPOHTMLReport = objGPMReport.result
End Function

Wscript.echo getGPOHTMLReport("normanbauer.com", "{6AC1786C-016F-11D2-945F-00C04fB984F9}") 'Default Domain Controllers Policy
Sub exportGPOHTMLReport(strDomain, strGPOCN, strOutFilename)
  Set objGPM = CreateObject("GPMgmt.GPM")
  set objGPMConstants = objGPM.GetConstants()
  set objGPMDomain = objGPM.GetDomain(strDomain, "", objGPMConstants.UseAnyDC)

  Set objGPO = objGPMDomain.GetGPO(strGPOCN)
  objGPO.GenerateReportToFile objGPMConstants.ReportHTML, strOutFilename
End Sub

exportGPOHTMLReport "normanbauer.com", "{6AC1786C-016F-11D2-945F-00C04fB984F9}", "C:\temp\export.html" 'Default Domain Controllers Policy

Functions used in the scripts above:

The function above generates the report of the specified gpo (you can find the cn of the gpo ["Unique ID"] in the Group Policy Management console on the details tab of a gpo, or in the System\Policies Container in Active Directory) and returns the html formatted result. The sub does almost the same but does not return the result but saves it to a file specified in strOutFilename.

You can do much more with the GPMgmt.GPM object – almost everything what the console can do, like creating, deleting and copying gpos, get and set wmi filters and set the gpo to be enabled or disabled on computer and/or user accounts.

How to mark a DPM tape containing active recovery points as free

When trying to mark a tape that contains active recovery points as free in Data Protection Manager you’ll receive an error like this:

The selected tapes cannot be marked as free because they belong to protection groups.
You need to stop protection of the associated protection groups before you can mark the tapes as free. The protection group that a tape belongs to is listed in the Protection Group column. (ID: 31116)

You can still force DPM to mark the tape as free by removing the recovery points. Use the following script in the DPM Powershell.

$barcode = "000012L4"     #type the barcode of the tape that shall be marked as free
$dpmserver = "DPMSERVER"  #type your dpm server's name here

$DPMLib = Get-DPMLibrary -DPMServerName $dpmserver
$tape = Get-Tape -DPMLibrary $DPMLib | Where-Object {$_.Barcode.Value -eq $barcode}

foreach ($RecoveryPoint in @(Get-RecoveryPoint -Tape $tape))
{
   Remove-RecoveryPoint -RecoveryPoint $RecoveryPoint -ForceDeletion -Confirm:$false
}

I chose to select the tapes by barcode. You can change the code easily to match your needs.

This script will delete all recovery points on the tape identified by its barcode – so please be careful…

How to fix broken images after mySite URL change in SharePoint 2010?

Here is a small PowerShell script to fix the pictureurl for all userprofiles after changing the mySite Url:

$mySiteWebapplicationUrl = "https://mysite.normanbauer.com/"
#current Url of your mySite website

$mySiteOldUrlValue = "http://mysitetest:80/"
#former Url where your pictures do not reside any more

$mySiteNewUrlValue = "https://mysite.normanbauer.com:443/"
#current Url where your images can be found now

$mySite = Get-SPSite $mySiteWebapplicationUrl
$SPServiceContext = Get-SPServiceContext $mySite
$userProfileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($SPServiceContext)
$userProfiles = $userProfileManager.GetEnumerator()

foreach ($userProfile in $userProfiles)
{
  #if pictureurl is not empty replace the old url part with the new one
  if ($userProfile["PictureURL"] -ne '')
  {
    $oldPictureUrl = $userProfile["PictureURL"].toString()
    $newPictureUrl = $oldPictureUrl.Replace($mySiteOldUrlValue, $mySiteNewUrlValue)
    write-host "oldPictureUrl = " $oldPictureUrl " --> newPictureUrl = " $newPictureUrl
    $userProfile["PictureURL"].Value = $newPictureUrl
    $userProfile.Commit()
  }
}

How to get detailed information on Windows boot and shutdown performance in PowerShell?

In Windows 7 administrators have the possibility to monitor boot and shutdown performance by reviewing event logs. The most common one is located at Event Viewer > Application and Services Logs > Microsoft > Windows > Diagnostics-Performance > Operational. Events with an ID of 100 for boot up and 200 for shutdown will give you some basic information on the general tab, for example, when did the last boot up or shutdown happen and how long took it to complete and more detailed information on the Details tab.

On the Details tab you can find even more. Here is how to get this information programatically with the help of powershell. Run the following commands from an elevated powershell:

$bootevents = Get-WinEvent -FilterHashtable @{logname="Microsoft-Windows-Diagnostics-Performance/Operational"; id=100}
$bootevent = [xml]$bootevents[0].ToXml()
$bootevent.Event.EventData.Data

$shutdownevents = Get-WinEvent -FilterHashtable @{logname="Microsoft-Windows-Diagnostics-Performance/Operational"; id=200}
$shutdownevent = [xml]$shutdownevents[0].ToXml()
$shutdownevent.Event.EventData.Data

These upper 3 lines will get all events from the named logfile with the event id 100. Afterwards in converts the first result to xml, which now can be used in different ways. At this time we just want to print it on the screen. The lower 3 lines will do the same for shutdown events with event id 200.

This is the boot result for my computer:

Name                                    #text
----                                    -----
BootTsVersion                           2
BootStartTime                           2012-01-10T07:33:36.656000300Z
BootEndTime                             2012-01-10T07:35:43.438676400Z
SystemBootInstance                      167
UserBootInstance                        122
BootTime                                78563
MainPathBootTime                        30263
BootKernelInitTime                      28
BootDriverInitTime                      1521
BootDevicesInitTime                     1809
BootPrefetchInitTime                    0
BootPrefetchBytes                       0
BootAutoChkTime                         0
BootSmssInitTime                        7424
BootCriticalServicesInitTime            534
BootUserProfileProcessingTime           4625
BootMachineProfileProcessingTime        10802
BootExplorerInitTime                    2383
BootNumStartupApps                      17
BootPostBootTime                        48300
BootIsRebootAfterInstall                false
BootRootCauseStepImprovementBits        0
BootRootCauseGradualImprovementBits     0
BootRootCauseStepDegradationBits        0
BootRootCauseGradualDegradationBits     0
BootIsDegradation                       false
BootIsStepDegradation                   false
BootIsGradualDegradation                false
BootImprovementDelta                    0
BootDegradationDelta                    0
BootIsRootCauseIdentified               false
OSLoaderDuration                        1026
BootPNPInitStartTimeMS                  28
BootPNPInitDuration                     1990
OtherKernelInitDuration                 1006
SystemPNPInitStartTimeMS                2990
SystemPNPInitDuration                   1340
SessionInitStartTimeMS                  4337
Session0InitDuration                    5181
Session1InitDuration                    1192
SessionInitOtherDuration                1050
WinLogonStartTimeMS                     11761
OtherLogonInitActivityDuration          691
UserLogonWaitDuration                   8463

And here is the shutdown result:

Name                                    #text
----                                    -----
ShutdownTsVersion                       1
ShutdownStartTime                       2012-01-07T15:06:38.501239300Z
ShutdownEndTime                         2012-01-07T15:07:03.559344800Z
ShutdownTime                            25058
ShutdownUserSessionTime                 2728
ShutdownUserPolicyTime                  37
ShutdownUserProfilesTime                84
ShutdownSystemSessionsTime              20852
ShutdownPreShutdownNotificationsTime    15732
ShutdownServicesTime                    5007
ShutdownKernelTime                      1477
ShutdownRootCauseStepImprovementBits    0
ShutdownRootCauseGradualImprovementBits 0
ShutdownRootCauseStepDegradationBits    0
ShutdownRootCauseGradualDegradationBits 0
ShutdownIsDegradation                   false
ShutdownTimeChange                      0

Have a look at this blog post to learn how to push this data into a SQL database with powershell.

How to change BitLocker recovery password with vbScript?

Related to my last post about how to change BitLocker recovery password from an elevated command prompt here is how you can achieve the same result with vbScript and WMI. This script is from Microsoft TechNet: BitLocker Drive Encryption Operations Guide: Recovering Encrypted Volumes with AD DS.

' Target drive letter
strDriveLetter = "c:"

' Target computer name
' Use "." to connect to the local computer
strComputerName = "."

' --------------------------------------------------------------------------------
' Connect to the BitLocker WMI provider class
' --------------------------------------------------------------------------------

strConnectionStr = "winmgmts:" _
                 & "{impersonationLevel=impersonate,authenticationLevel=pktPrivacy}!\\" _
                 & strComputerName _
                 & "\root\cimv2\Security\MicrosoftVolumeEncryption"

On Error Resume Next 'handle permission errors

Set objWMIService = GetObject(strConnectionStr)

If Err.Number <> 0 Then
     WScript.Echo "Failed to connect to the BitLocker interface (Error 0x" & Hex(Err.Number) & ")."
     Wscript.Echo "Ensure that you are running with administrative privileges."
     WScript.Quit -1
End If

On Error GoTo 0

strQuery = "Select * from Win32_EncryptableVolume where DriveLetter='" & strDriveLetter & "'"
Set colTargetVolumes = objWMIService.ExecQuery(strQuery)

If colTargetVolumes.Count = 0 Then
    WScript.Echo "FAILURE: Unable to find BitLocker-capable drive " &  strDriveLetter & " on computer " & strComputerName & "."
    WScript.Quit -1
End If

' there should only be one volume found
For Each objFoundVolume in colTargetVolumes
    set objVolume = objFoundVolume
Next

' objVolume is now our found BitLocker-capable disk volume

' --------------------------------------------------------------------------------
' Perform BitLocker WMI provider functionality
' --------------------------------------------------------------------------------

' Add a new recovery password, keeping the ID around so it doesn't get deleted later
' ----------------------------------------------------------------------------------

nRC = objVolume.ProtectKeyWithNumericalPassword("Recovery Password Refreshed By Script", , sNewKeyProtectorID)

If nRC <> 0 Then
     WScript.Echo "FAILURE: ProtectKeyWithNumericalPassword failed with return code 0x" & Hex(nRC)
     WScript.Quit -1
End If

' Removes the other, "stale", recovery passwords
' ----------------------------------------------------------------------------------

nKeyProtectorTypeIn = 3 ' type associated with "Numerical Password" protector

nRC = objVolume.GetKeyProtectors(nKeyProtectorTypeIn, aKeyProtectorIDs)

If nRC <> 0 Then
     WScript.Echo "FAILURE: GetKeyProtectors failed with return code 0x" & Hex(nRC)
     WScript.Quit -1
End If

' Delete those key protectors other than the one we just added.

For Each sKeyProtectorID In aKeyProtectorIDs
     If sKeyProtectorID <> sNewKeyProtectorID Then
          nRC = objVolume.DeleteKeyProtector(sKeyProtectorID)
          If nRC <> 0 Then
               WScript.Echo "FAILURE: DeleteKeyProtector on ID " & sKeyProtectorID & " failed with return code 0x" & Hex(nRC)
               WScript.Quit -1
          Else
               ' no output
               'WScript.Echo "SUCCESS: Key protector with ID " & sKeyProtectorID & " deleted"
          End If
     End If
Next

WScript.Echo "A new recovery password has been added. Old passwords have been removed."

' - some advanced output (hidden)
'WScript.Echo ""
'WScript.Echo "Type ""manage-bde -protectors -get " & strDriveLetter & " -type recoverypassword"" to view existing passwords."

How to display Active Directory stored user account pictures in Windows?

This topic is little more complex and we’ll need different steps to accomplish this feature.
First at all you need the pictures for your user objects in the directory. You can use ADSI Edit, Powershell or 3rd party software to put pictures in AD. My blog post “How to save a user picture in Active Directory with vbScript?” will do this for you using vbScript.
Second you’ll need a small application that sets a picture on your harddrive as the user account picture. I tested this only with Windows 7 – so there is no guarantee that this works with other versions too. Why an application? Because there is no documented way of setting an individual user account picture programmatically in vbScript – neither via registry nor via file system.
My blog post “How to set the Windows 7 user account picture programmatically?” covers that small application.
Now that we have the pictures in Active Directory and developed a small application that sets an image file as user account picture we just need one more step between both. We need a vbScript that runs at logon, loads the picture from AD, puts it on the disk and calls our application. Here we go:

Function LoadPictureFromAD(szADsPath, szSaveFileName)
	Dim objUser, bytesRead, adoStreamWrite
	Const adTypeBinary = 1, adSaveCreateOverWrite = 2

	Set objUser = GetObject(szADsPath)
	bytesRead = objUser.Get("thumbnailPhoto")

	Set adoStreamWrite = CreateObject("ADODB.Stream")
	adoStreamWrite.Type = adTypeBinary
	adoStreamWrite.Open
	adoStreamWrite.Write(bytesRead)
	adoStreamWrite.SaveToFile szSaveFileName, adSaveCreateOverWrite
	adoStreamWrite.Close
End Function

Set wshShell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")

workingdir = Replace(wscript.scriptfullname, Wscript.scriptname, "")

Set wshNetwork = WScript.CreateObject("WScript.Network")
username = wshNetwork.UserDomain  & "\" & wshNetwork.UserName
Set objSysInfo = CreateObject("ADSystemInfo")
strUserName = objSysInfo.UserName
dn = "LDAP://" & strUserName

path = wshShell.ExpandEnvironmentStrings("%temp%") & "\"
filename = path & "uap.jpg"

LoadPictureFromAD dn, filename
wshshell.run workingdir & "useraccountpicture.exe " & username & " " & filename, 0, true

This script contains the function LoadPictureFromAD which expects the user’s distinguished name and a filename where the resulting picture will be saved to. Inside the function we connect to AD, get the user’s object, read the attribute thumbnailPhoto, copy it to a previously created stream object and save it in a file identified by szSaveFileName.
The script itself assumes that the useraccountpicture.exe application resists in the same location as the script. So we set the working directory to the scripts location. After that we need the user’s name, it’s domain and it’s distinguished name. Then we create the filename for the image, this is simply the temp folder with “uap.jpg” as filename. With the distinguished name and the filename we can call the LoadPictureFromAD function and finally we run the useraccountpicture application with the username and the filename as parameters.

Save the script as .vbs – run it – have a look at the start menu – that’s all…

Now you can use this script as a logon script within group policies and all your users can find their photos in the start menu and on the lock screen.

How to save a user picture in Active Directory with vbScript?

Active Directory offers the possibility to save pictures in a user’s object. These pictures can then be used in Outlook, Sharepoint or even self-written applications. Here is how you can do it:

Function SavePictureToAdFromUrl(szADsPath, szUrl)
	Dim objUser, bytesRead, adoStreamRead
	Const adTypeBinary = 1

	Set xml = CreateObject("Microsoft.XMLHTTP")
	xml.Open "GET", szUrl, False
	xml.Send

	If xml.status = 200 Then
		Set adoStreamRead = CreateObject("ADODB.Stream")
		adoStreamRead.Type = adTypeBinary
		adoStreamRead.Open
		adoStreamRead.Write xml.responseBody
		adoStreamRead.Position = 0
		bytesRead = adoStreamRead.Read()
		adoStreamRead.Close

		Set objUser = GetObject(szADsPath)
		objUser.Put "thumbnailPhoto", bytesRead
		objUser.SetInfo
	End If
End Function

This function will read a picture resource from a url (szUrl) and append it to the thumbnailPhoto attribute in the user object identified by szADsPath.
First we open the url and see if we’ll get a status 200 (OK). If so a binary stream object is created and the picture data from the url is written into it. After that we transfer the data from the stream object into a variable, then we create an object from the given szADsPath and finally put the content of the bytesRead variable into the thumbnailPhoto attribute of the user object.
If you do not have a website providing the picture you can do this also with files on a file system.

Function SavePictureToAD(szADsPath, szLoadFileName)
	Dim objUser, bytesRead, adoStreamRead
	Const adTypeBinary = 1

	Set adoStreamRead = CreateObject("ADODB.Stream")
	adoStreamRead.Type = adTypeBinary
	adoStreamRead.Open
	adoStreamRead.LoadFromFile szLoadFileName
	bytesRead = adoStreamRead.Read()
	adoStreamRead.Close

	Set objUser = GetObject(szADsPath)
	objUser.Put "thumbnailPhoto", bytesRead
	objUser.SetInfo
End Function

This one is almost the same function, but it does not expect an url as parameter but a filename – the rest of the function stays the same.

When using one of these functions please make sure that the resource identified by either url or filename is a valid picture file like jpeg or png. Neither Active Directory nor my functions will validate the files!
Please also note that the attribute thumbnailPhoto is for thumbnails – therefor you should use small pictures. Recommended are jpeg pictures with dimensions of 128×128 pixels an a size not exceeding 10KB. The limit of the thumbnailPhoto attribute is 100KB.

How to set the Windows 7 user account picture programmatically?

Windows 7 offers the possibility to show small user account pictures, for example in the start menu or on the lock screen. Since there is no documented way of setting this picture programmatically here is a solution for developing a small application that can do this for you.
You’ll simply need Visual Studio – either with C# or Visual Basic support. You can get your free edition here: http://www.microsoft.com/visualstudio/express. Create a new console application, copy and paste the following source code an build the solution. The resulting application will be able to set the user account pictures for you.

C# source:

using System;
using System.Runtime.InteropServices;

namespace useraccountpicture
{
    class Program
    {
        [DllImport("shell32.dll", EntryPoint = "#262", CharSet = CharSet.Unicode, PreserveSig = false)]
        public static extern void SetUserTile(string username, int notneeded, string picturefilename);

        [STAThread]
        static void Main(string[] args)
        {
            if (args.Length == 2)
            {
                SetUserTile(args[0], 0, args[1]);
            }
        }
    }
}

Visual Basic source:

Imports System.Runtime.InteropServices

Module useraccountpicture

    <DllImport("shell32.dll", EntryPoint:="#262", CharSet:=CharSet.Unicode, PreserveSig:=False)> _
    Private Sub SetUserTile(ByVal username As String, ByVal notneeded As Integer, ByVal picturefilename As String)
    End Sub

    Sub Main(ByVal args As String())
        If (args.Length = 2) Then
            SetUserTile(args(0), 0, args(1))
        End If
    End Sub

End Module

Now run it on a command line, for example:
useraccountpicture.exe domain\username picturefilename.jpg

Please note that this will not affect the logon screen. Since windows cannot know which user will logon it cannot display a picture.

How to connect to and read data from Mailboxes, Calendars, … via Exchange Web Services and vbScript?

I offen wondered how I can programmatically read items from mailboxes stored within Microsoft Exchange Server. Some time ago I used Outlook with some macros that accessed the information I needed. But what if Outlook does not run? Or what if the computer crashed – who would logon and start Outlook? Sure you can find workarounds for every problem. But this never seemed to be a ‘cool’ solution to me. In the past few weeks – after I got my Exchange 2010 MCITP – I started to have a look at EWS – Exchange Web Services (available since Microsoft Exchange Server 2007). This one seemed to be pretty simple and also a stable solution to all my needs. Here is how I do it all now with vbscript…

There is not much you’ll need to connect to Exchange, just a http connection to your Client Access Server, a user with credentials and access to the mailbox you want to read, a soap xml request and some lines of code that will parse the response for you.

First, you’ll need to know the dns name or the ip of your Client Access Server – in this example I’ll you EXCAS.domain.tld.
Now we create some objects and define the url to the CAS and username and password to access it:

Dim objXmlHttp, xmlDoc

Const strUrl = "https://EXCAS.domain.tld/ews/exchange.asmx" 'URL to CAS
Const strUser = "domain.tld\myexchangeuser" 'Domain and Username to authenticate with
Const strPassword = "myExchangeUSeRsP4ssw0rd"
Const strEmailAddress = "mailboxtoreadfrom@domain.tld" 'Mailbox that you want to read data from

Set objXmlHttp = CreateObject("Microsoft.XMLHTTP") 'this will send the request to EWS
Set objXmlDoc = CreateObject("MSXML2.DOMDocument") 'this will parse the response for us

Now it is time to decide what we want to do, or better: we need to create the request telling EWS what to do. In this example I want to know about every calendar item in a certain time span. But you can do much more:

Since we want to crawl for many items we will use the FindItem Operation. Following this link you’ll get a complete overview how the request will look like. In common every operation that can be used with Exchange Web Service is really well documented!
As you can see the basic xml soap request is:



  
    
      
        IdOnly
      
      
        
      
    
  

We just need to make some modifications to this example – we want to read all properties in the calendar for example, we do want to connect to another mailbox and we do want to use a certain time span not the whole calendar. So the soap request would look like this (changes marked bold):



  
    
      
        AllProperties
      
      
      
        
          
            mailboxtoreadfrom@domain.tld
          
        
      
    
  

Now we need to connect to EWS, post this request, wait for the answer and parse it. Since this is a complex xml structure we just need to break it down to that level the information is stored in – in this example it is the “t_Items” node.

objXmlHttp.open "POST", strUrl, False, strUser, strPassword
objXmlHttp.setRequestHeader "Content-Type", "text/xml"
objXmlHttp.send strXmlData 'this is the soap request from above
If objXmlHttp.Status = "200" Then 'if the request was successful go on
  If objXmlDoc.loadXML(objXmlHttp.responseText) Then
    Set soap_Body = objXmlDoc.documentElement.childNodes.item(1) 'soap_body is the second node below the document root
    Set m_FindItemResponse = soap_Body.childNodes.item(0) 'm_FindItemResponse is the first node below soap_body
    Set m_ResponseMessages = m_FindItemResponse.childNodes.item(0) 'and so on...
    Set m_FindItemResponseMessage = m_ResponseMessages.childNodes.item(0)
    Set m_RootFolder = m_FindItemResponseMessage.childNodes.item(1)
    Set t_Items = m_RootFolder.childNodes.item(0)

    For Each calendarItem In t_Items.childNodes
      Dim t_Subject, t_Start, t_End, t_LegacyFreeBusyStatus, t_Location, t_IsRecurring, t_CalendarItemType, t_MyResponseType, t_Organizer
      t_ItemId = calendarItem.getElementsByTagName("t:ItemId").item(0).getAttribute("Id")
      t_Subject = calendarItem.getElementsByTagName("t:Subject").item(0).text
      t_Start = calendarItem.getElementsByTagName("t:Start").item(0).text
      t_End = calendarItem.getElementsByTagName("t:End").item(0).text
      t_LegacyFreeBusyStatus = calendarItem.getElementsByTagName("t:LegacyFreeBusyStatus").item(0).text
      t_Location = calendarItem.getElementsByTagName("t:Location").item(0).text
      t_IsRecurring = calendarItem.getElementsByTagName("t:IsRecurring").item(0).text
      t_CalendarItemType = calendarItem.getElementsByTagName("t:CalendarItemType").item(0).text
      t_MyResponseType = calendarItem.getElementsByTagName("t:MyResponseType").item(0).text
      t_Organizer = calendarItem.getElementsByTagName("t:Organizer").item(0).getElementsByTagName("t:Mailbox").item(0).getElementsByTagName("t:Name").item(0).text

      'Here you can do anything you like with all this information
    Next
  End If
Else
  wscript.echo "Error: " & objXmlHttp.Status & " - " & objXmlHttp.statusText 'If status is not 200
End If

Documentation for functions, methods and properties used in this post: