Marco Di Feo – Blog Development, Hobby, Everything

15Feb/127

Powershell – Rename domain computer remotely (within an active directory domain as well)

I wanted to rename a couple of computers within our active directory. After some research i figured out thats it seems to be nearly impossible to rename a computer by just touching one object: the ad computer object, or the computer (client) itself. My first thought was "ok, you have to rename both objects, rejoin the computer and hope everything works". But that's no solution, that makes me happy, because the more steps you do, the more problems can occure. For examples, what happens if the computer has to reboot, after renaming to get correct rejoined? Do i have to create a local admin account at the clientside to have permissions after the computer lost his connection to ad? And so on...
I played around, renamed the ad object, rebooted it - negative, the computer has to be joined again. After that i tried it the "bottom up" way by renaming the computer by hand, and rebooted it instantly. While the computer was shutting down i noticed, that the computerobject in active directory was renamed before the computer was finished with its shutdown process.
So i tried this several times and every time the ad computerobject was renamed properly. YAY!
I had my solution. It can't get more easy to rename a computer without rejoining it.

After this conclusion i tried to do it remotly with powershell, so i googled and found some sites about using netdom.exe. But, yep, right, calling a remote program was not the way i want to solve this problem :) . I found some information on using wmi that suits me, so i started writing a powershell script to test it. As expected it's a bit complicated to rename a computer within a domain by wmi. You have to overcome 3 "hurdles":
- Use Authentication
- Using username and passwort of an administrative account
- Reboot the computer instantly after renaming

Here is a script, that does everything you want. Rename the computer and reboot it if renaming was successfull. Look at line 32 and fill in your admin credentials (~domain admin)

#############################################################################################
# Parameterlist
#############################################################################################
param([string] $computername,$newcomputername)

#############################################################################################
# POWERSHELL Ping tool to check if a computer/ip/hostname is online
#############################################################################################
function isComputerOnline( $comp ){
    trap [Exception] {
        return $false
   }
    if ( $(new-object system.net.networkinformation.ping).send( $comp ).status -eq "Success" )
    {
        return $true
    }else{
        return $false
    }
}

#############################################################################################
# Remote Rename Computer by WMI
#############################################################################################
function COMPUTER_RENAME([string] $oldComputerName, [string] $newComputerName){
	# should implement further checks for the computername. If computername uses illegal characters and if computername is longer than 15 characters
	# More information here: http://labmice.techtarget.com/articles/computernaming.htm
    $oldComputerName = $oldComputerName.replace(" ","");
    $newComputerName = $newComputerName.replace(" ","");
    # // Check if the computer is online
    if ( isComputerOnline $oldComputerName ){
		# // Check the active directory if a computer with your provided newcomputername already exists.
		# // If you want to use the script without active directory just comment (use: #) this line and the corresponding "else" loop
        if ( ! (Get-ADObject -ldapfilter  "(CN=$newComputerName)") ){
            # // Handle every upcoming error as a stop failure, so you can trap it
            $ErrorActionPreference = "Stop"
            try{
                # // Get the WMI Object of the remote computer
                $ComputerWMIObject = Get-WmiObject Win32_ComputerSystem -ComputerName "$oldComputerName" -Authentication 6
                if ( $ComputerWMIObject ){
                    # // Rename the Computer Object with your or some admin credentials (Yes, Passwort is the second parameter and username the third :)  )
                    $result = $ComputerWMIObject.Rename("$newComputerName","[PASSWORD]","[USERNAME]")
                    # // Switch Case for the returnvalue of computer renaming function
                    switch($result.ReturnValue)
                    {
                        0 {
                            # // Reboot the computer instantly if renaming was successfull
                            Get-WmiObject Win32_OperatingSystem -ComputerName "$oldComputerName" |
                            ForEach-Object {$restart = $_.Win32Shutdown(6)}
                            write-host "Computer $oldComputerName was renamed ($newComputerName) and restarted"

                        }
                        5 { write-host "Computer was not renamed. Please check if you have admin permissions (ReturnCode 5)"; exit; }
                        default { write-host "ReturnCode $($result.ReturnValue)"; exit;}
                    }
                }else{
                    write-host "Couldn't create WMI Object on $oldComputerName"
                }
            }catch{
                write-host $_
            }
        }else{
            write-host "There is already a computerobject with the name $($newComputerName)"
        }
    }else{
        write-host "Computer is offline!"
    }
}

if ( $computername -and $newcomputername ){
	COMPUTER_RENAME $computername $newcomputername
}else{
	write-host ""
	write-host "Script Usage:
.ps1 -computername ""Current-Computer-Name"" -newcomputername ""New-Computer-Name"""
	write-host ""
}

To use this script just copy the source code to a .txt file and rename it to [whateverYouWant].ps1.
Now start your Powershell and go to the directory where your script is and start it with the following command:

.\myrenamescript.ps1 -computername "CurrentCompName" -newcomputername "NewCompName"

There is no check on the computername yet, so every computername you submit is more or less valid.
More information about the limitations of a computername can be found here: http://support.microsoft.com/kb/909264/en-us

Feel free to use this script and leave me a comment!

13Okt/110

Powershell – SCCM – Readvertise a previously installed softwarepackage remotly (not from console)

Ever came to the situation, that you have to rerun an advertisement on a computer after a failed installation with SCCM? This could happen if you want to install flashplayer or adobe acrobat or some kind of software that is frequently used by the user. We had problems on installing software while wsus was installing its updates and the msi installer said "Hmm... perhaps, another installation is running already, so I cant do anything for you (BLAME!), sorry". I hate this message, because its a pain in the ass to readvertise these failed softwarepackages on a single computer.

As some posts earlier mentioned, I am writing an administration webapplication to administrate our environment. This includes softwaredeployment too. We address our softwarepackages by Active Directory groups. Every time we create a new softwarepackage, we create a seperate computer collection in SCCM and link it to a newly created ad group. Every member of this ad group (computers for instance) gets the linked software by SCCM.

While it's a bit difficult to get some automation into the readvertisement with all these groups and links, we wrote a script, that is triggered by my webgui and does this job on the client computer side for us.

As you can imagine, the first prerequisite for this script to run is: Yes, admin privilages on the client computer. If you don't have admin rights, just do the conventional way: walk over there and install the software by hand.

What does your script in detail?

That was the question I was waiting for. It's a bit complicated, but I try to explain what my script does.

These are the prerequisites:

  1. Admin privilages on the client computer
  2. An administrative powershell
  3. A computername, netbiosname, dns name, or ipaddress of your machine
  4. The advertisementID of your advertisement. But pay attention! The used advertisementID is the one of your computer and not the one from your SCCM server. You can get your advertisementID at your sccm client computer reports. (It's the AdvertisementID you can see at your sccm console). If you want to get the advertisementID by packageID just scroll down a bit to the topic "Get the client advertisementID by packageID"

If you have your (client) advertisementID (should look something like XXX00012, where XXX is your sitecode) we can now start on how the script actually works.
This script does everything by using (the mighty) WMI. First we query the CCM_Softwaredistribution to change a value of a current advertisement.

This key ist called "ADV_RepeatRunBehavior". As the name says, this key controls, when your advertisement have to rerun. Normally this key has "RerunIfFail" as value. But the advertisement doesn't rerun over and over again until the installation is successfull (thats why you are here and reading this post).
We set the value of our advertisement from "RerunIfFail" to "RerunAlways".
Afterwards we query the CCM_Scheduler_ScheduledMessage to get the ScheduledMessageID (which looks like a GUID with your advertisementID as first segment).

This id is used for the scheduler to identify your advertisement.
So all we have to do is to trigger the SMS_Client (method: TriggerSchedule), pass through our ScheduledMessageID and fire it up. After submitting our request the SMS_Client on your computer comes up and with "hey, some kind of software needs to be installed (if you configured this in SCCM)".

All we have to do after your advertisement is rerunning is to reset "ADV_RepeatRunBehavior" to its previous state which was "RerunIfFail".
I don't want to destroy anything, so I read this value before changing it at the beginning of my script and put it back in, after we are done with our readertisement.

Im not finished with this script yet, it's a little beta script to test the rerun on my local computer. There is no exception handling, no trapping, no checking on the return values.
I like this kind of raw script, because you can take what you want without renaming all functions, and rewrite all the exceptional thing. Let me know (@comments) if you want to get the final script with all the exception handing and the ping checker. I added a beta version of the rerun script at the end of this post.

Very important

You can't rerun all of your previously commited advertisement. The SMS_Client delete your advertisements after... I don't know on which conditions these items are deleted. Every rerunable advertisement is stored in the CCM_SoftwareDistribution database. So you have to check first if your advertisement is available.

Get the client advertisementID by packageID

If you don't want to find the client advertisementID by reading your reports you can get it by quering the "CCM_Softwaredistribution" for the fields "PKG_Name" (the SCCM packagename) or "PKG_PackageID" (the SCCM PackageID you can see at your SCCM console).
To get a list of all readvertisable advertisements, just use this WMI query on your client.

# // Query the CCM_Softwaredistribution database to get all readvertisable advertisment IDs
get-wmiobject -query "SELECT ADV_AdvertisementID, PKG_Name, PKG_PackageID FROM CCM_Softwaredistribution " -namespace "root\CCM\Policy\Machine\ActualConfig" -Computer $Computer

The Script

# // Kudos
# // Author: Marco Di Feo
# // Website: http://www.marco-difeo.de
# // Scriptname: rerun_sccm_advertisement_on_client.ps1
# //
# // Usage: This script can be modified to fit your needs
# // Pingback: http://marco-difeo.de/2011/10/13/powershell-sccm-readvertise-a-previously-installed-softwarepackage-remotly/
# //
# // #######################################################################################
# // Declaration
# // #######################################################################################

# // Your SCCM advertisementID
$ADVID = "XXX200BC"
# // Computername on which the magic takes place ( . is the local computer)
$Computer = "."
# // #######################################################################################
# // Script start
# // #######################################################################################
#
# // Variable to store your current repeat run behaviour (mostly RerunIfFail)
$CurrentRepeatRunBehaviour = ""
# // Should check if the computer is already online or not.
# // Query the CCM_Softwaredistribution class to set the ADV_RepeatRunBehavior. This variable defines, when your advertisement has to rerun
# // Mostly, this key has the value "RerunIfFail"
$Advertisement = get-wmiobject -query "SELECT * FROM CCM_Softwaredistribution WHERE ADV_AdvertisementID LIKE '$($ADVID)' " -namespace "root\CCM\Policy\Machine\ActualConfig" -Computer $Computer -Authentication PacketPrivacy -Impersonation Impersonate
# -> if returnvalue.length == 1
# // Store your current ADV_RepeatRunBehavior value
$CurrentRepeatRunBehaviour = $Advertisement.ADV_RepeatRunBehavior
# // Set the ADV_RepeatRunBehavior to RerunAlways. We set this back to its previous value after triggering the SCCM_Client to rerun your advertisement
$Advertisement.ADV_RepeatRunBehavior = "RerunAlways"
# // Commit your changes
$Advertisement.put()

# // Get the ScheduledMessageID. You need this ID to trigger the scheduler to process your advertisement to rerun
$ScheduledMessageID = (get-wmiobject -query "SELECT ScheduledMessageID FROM CCM_Scheduler_ScheduledMessage WHERE ScheduledMessageID LIKE '$($ADVID)-%' " -namespace "root\CCM\Policy\Machine\ActualConfig" -Computer $Computer -Authentication PacketPrivacy -Impersonation Impersonate).ScheduledMessageID
# -> if != ""

# // Get Root Namespace of your SMS_Client for triggering the rerun
$SMSClient = [WmiClass]"\\$($Computer)\ROOT\ccm:SMS_Client"
# // Get the ParameterList from the TriggerSchedule method
$ParameterList = $SMSClient.psbase.GetMethodParameters("TriggerSchedule")
# // Set sScheduleID to your previously determined ScheduledMessageID
$ParameterList.sScheduleID = $ScheduledMessageID
# // Invoke the TriggerSchedule WMI Method and commit your ParameterList (which only contains your ScheduledMessageID)
$SMSClient.psbase.InvokeMethod("TriggerSchedule",$ParameterList,$NULL)

# // After triggering the TriggerSchedule your advertisement will rerun immediately, but lets wait a sec or two
sleep 3

# // Reset the ADV_RepeatRunBehaviour field of CCM_Softwaredistribution, so no undesired advertise will rerun
# // Query your CCM_Softwaredistribution
$Advertisement = get-wmiobject -query "SELECT * FROM CCM_Softwaredistribution WHERE ADV_AdvertisementID LIKE '$($ADVID)' " -namespace "root\CCM\Policy\Machine\ActualConfig" -Computer $Computer -Authentication PacketPrivacy -Impersonation Impersonate
# // Set the ADV_RepeatRunBehavior to the previous value
$Advertisement.ADV_RepeatRunBehavior = $CurrentRepeatRunBehaviour
# // Commit changes
$Advertisement.put()
# // YAY, we are done!
# // Party!
# //

Here you can download a much more final version of the rerun script: Powershell Readvertisement Script

10Okt/110

Active Directory – Supersonic and the directory searcher

Whats the difference between supersonic and the directory searcher? Nothing, because if you get over ~1000 you crash into a wall. :)
Last week we had to deal with the limitations on the directory searcher.
We performed a user search on an organizational unit (subtree) with more than 1000 users beneath. We where astonished that our userobject only contained 1000 items every time we did the search. I wrote a little test script in powershell to reproduce this behaviour and to see if this limitation is a C# problem or not.

So I wrote this script:

import-module activedirectory

$intCounter = 0

$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = ("(objectclass=user)")
$colResults = $objSearcher.FindAll()

foreach ( $objResult in $colResults ) {$intCounter = $intCounter + 1}
write-host "Found $intCounter entries"

As expected, powershell returned only 1000 objects. Then we tried to find out why and looked at the $objSearcher property list.

$objSearcher | get-member

$objSearcher | get-member Active Directory

As you can see, there are two interesting properties: sizelimit and pagesize. So I played around with these two properties and found some explanations for them. Sizelimit is the limit for the maximum returned results, but you can't set this property above 1000. So I looked at the second property, pagesize. This property sets the maximum result items per returned page. So all you have to do is, set sizelimit to 0 and set pagesize to 1000 and you will get all of your items.

import-module activedirectory

$intCounter = 0

$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.sizelimit = 0
$objSearcher.pagesize = 1000
$objSearcher.Filter = ("(objectclass=user)")
$colResults = $objSearcher.FindAll()

foreach ( $objResult in $colResults ) {$intCounter = $intCounter + 1}
write-host "Found $intCounter entries"

If you set pagesize to 1000 and expect more than 1000 returned items the active directory returns your first 1000 items, then pause for a split and returns the next x items and so on until all found items are returned. This property has a appreciable impact on your searchresult duration.

After some research I found some tutorials on how to increment these limitations in the active directory, but i don't know what impact such a change would have, so i can live with the pagesize property ;)

26Aug/110

Windows XP – Offer Remote Desktop Assistance with predefined ip

If you want to automate the remote desktop assistance (not remote desktop) with Windows XP you run into some problems. First of all I ran into the problem that "rcimlby.exe", which seems to be a good point to start lacks with its commandline parameter. I thought that "rcimlby.exe" has a hidden parameter to submit a computer netbios name, or a ipaddress to connect directly to the computer after launching the application, but after debugging the exe I didn't find any indication for a computerobject parameter, so I started to look elsewhere and found a pretty simple solution.

As it turns out, rcimlby.exe calls a help website (Windows XP help) from where you can start a remote desktop assistance. But this website hasn't a parameter for the computerobject as well and it seems to be impossible to call this website within the help explorer, so I traced the way rcimlby.exe calls this website and found this call:

c:\WINDOWS\pchealth\helpctr\binaries\helpctr.exe -FromStartHelp
-url hcp://CN=Microsoft%20Corporation,L=Redmond,S=Washington,C=US/Remote%20Assistance/Escalation/Unsolicited/UnSolicitedRCUI.htm

But there is no way to add a parameter for the computerobject, so I started to find out where these weird (and never used :D ) html helpfiles are stored (especially where UnSolicitedRCUI.htm is stored)

23Aug/110

SCCM – SQL Query for Computer Information (statistics)

I am developing an administrative webgui to give our admins access to different services to manage their users and computers in our new Active Directory environment. We came across that this is the best solution to automate so many things like creating home folder, creating profile folder, creating terminalserver profile folder, set ACL to folders, create and publish printers to the AD, set quota for different kind of folders, distribute software via SCCM by AD groups and so on. It is necessary that every admin can only see objects that belong to his department. After developing all these base functionality we came to the conclusion that the information that is stored in the SCCM database is really informative for the admins, so we created some selects for the database to get the information about computerobjects, softwarestatus, hardwareinformation, etc...

Additional we thought it would be a great idea to build some kind of dashboard to get an overview over our environment and whats going on there, so we played around with the SQL Adminstudio and got some good looking SQL queries, implemented these and display the information in some pie charts (highcharts.com).

2Aug/116

SCCM – Delete computer object via powershell and wmi [edit]

After one hour of research i found a solution on how to delete a computer object in sccm from a remote computer via Powershell. First of all you need to get the computer object from your sccm namespace "ROOT\SMS\site_[yoursite] -> SMS_R_SYSTEM.
All machine items are stored in SMS_R_SYSTEM, so i used a wmi query with a filter to get the object i need.

There are several ways you can get the computer object, another example is a WMI query, that returns the object.

// Powershell
$compObject = get-wmiobject -query "select * from SMS_R_SYSTEM WHERE Name='[computername]'" -computername [sccm-server] -namespace "ROOT\SMS\site_[yoursite]"

You should check if the $compObject is empty or not and if not you can just use psbase to delete the object from the sccm server completly (not only from the collection) by using this statement

// Powershell
$compObject.psbase.delete()

 

How to deal with multiple objects [edit]

Special thanks to Leeni, who wrote a comment at this post on how to deal with multiple entries at your SCCM server. If your WMI query returns more than one object you have to handle this a bit different.
To check if your query returned more than one object try this:

$compObject.psbase.length

or

$compObject.psbase.syncroot

Both properties are only set if your result contains more than one object, so you can easily check if these objects are empty or not.

If they are not empty you have to delete your $compObject this way

$compObject.psbase.syncroot | % { $_.delete() }

This pipes your syncroot object to a foreach loop (% is an alias for foreach) and delete every object one after another.