Using PowerShell to find all groups with members in a different domain

Yesterday I was asked by the Security Team to help them out with a project they were working on.  There is an OU with over 2,000 groups in it and they needed to find the names of all the groups whose members where in a different domain. 

#This script requires the Microsoft 'ActiveDirectory' module to be loaded.  
#The module is part of the Win7 RSAT and comes standard on Server 2008 R2
$Erroractionpreference = "SilentlyContinue" # For whatever reason, there are groups that can't be bound to.  
											# I will guess these are orphaned. In either case this line prevents errors from being displayed
foreach ($group in Get-ADGroup -SearchBase 'OU=SOMETHING,OU=GROUPS,OU=SOMETHING,OU=BUSINESSUNIT,DC=EmptyGarden,DC=info' -Filter *)
	{
	foreach ($user in Get-ADGroupMember -Identity $Group.Name)
		{
		if ($user.distinguishedName -notlike '*AnotherDomain,*') 
			{
			$group.name
			break # We only care if one user is a member of a different domain.  Thus, once we find one, we get out of the IF loop and grab another group.
			}
		}
	}

Powershell–Getting information from AD into the SCCM database

Recently I was asked to deliver a report on the count of machines in AD and their location.  In our environment we use a computer name identifier to designate location.  But, you could use OU information or eve AD Site information.  So, I wrote a PowerShell script that grabs all three and puts the information into SQL.  You can then merge the tables together and create a view.  You may be asking, “doesn’t SCCM System Discovery provide this information?”  Well, yes and no.  My PowerShell script returns more computers than SCCM is discovering.  Plus, the PowerShell script also returns information from AD Sites and Services as well as OU Description that can’t be obtained using SCCM discovery.

Get-ADComptuerInformation

Import-Module[DIRECTORY WHERE THE SCCM MODULE IS]\SCCM-Commands.psm1'
add-pssnapin Quest.ActiveRoles.ADManagement
Set-QADPSSnapinSettings -DefaultSizeLimit 100000
$sccm = Connect-SCCMServer
# Get all of the domain names
# Connect to RootDSE
$rootDSE = [ADSI]"LDAP://RootDSE"
# Connect to the Configuration Naming Context
$configSearchRoot = [ADSI]("LDAP://" + `
$rootDSE.Get("configurationNamingContext"))
# Configure the filter
$filter = "(NETBIOSName=*)"
# Search for all partitions where the NetBIOSName is set
$configSearch = New-Object DirectoryServices.DirectorySearcher($configSearchRoot, $filter)
# Configure search to return dnsroot and ncname attributes
$retVal = $configSearch.PropertiesToLoad.Add("dnsroot")
$retVal = $configSearch.PropertiesToLoad.Add("ncname")
#$configSearch.FindAll() | Select-Object @{n="dnsroot";e={$_.Properties.dnsroot}},  @{n="ncname";e={$_.Properties.ncname}}
#$DomainNames = foreach ($i in $configSearch.FindAll()) {$i.Properties.ncname}
$DomainNames = foreach ($i in $configSearch.FindAll()) {@{[string]$i.Properties.ncname = [string]$i.Properties.dnsroot}}
# Get computer information from AD into SQL
$conn = New-Object System.Data.SqlClient.SqlConnection("Data Source=[SQL SERVER NAME]; Initial Catalog=[SCCM DATABASE NAME]; Integrated Security=SSPI")
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = "delete from ADComputerInformation"
$cmd.ExecuteNonQuery()

foreach ($domain in $DomainNames) {
	#$LDAPDomainName = $domain.Keys
	#$LDAPDomainName = "`'$LDAPDomainName'"
	#[string]$DNSDomainName = $domain.Values
	$ADService = Connect-QADService -Service $domain.Values
	$ComputersInDomain = Get-QADComputer -SearchRoot $ADService.DefaultNamingContextDN -service $ADService.Domain.Name -SearchScope Subtree -Inactive:$false |
	Select OperatingSystem, ParentContainerDN, Name, domain
	# Write to SQL

	foreach ($i in $ComputersInDomain) {
		$cmd = $conn.CreateCommand()
		$OperatingSystem = $i.OperatingSystem.ToString()
		$ParentContainerDN = $i.ParentContainerDN.ToString()
		$Name = $i.Name.ToString()
		$domain = $i.domain.ToString()
		$cmd.CommandText ="INSERT ADComputerInformation VALUES ('$OperatingSystem','$ParentContainerDN', '$Name', '$domain')"
		$cmd.ExecuteNonQuery()
		}
	Clear-Variable computersindomain
	}
$conn.Close()


# Old working area
#$var = Get-QADComputer -SearchRoot['LDAP DOMAIN] -SearchScope Subtree -SizeLimit 1 -Inactive:$false |
#	group operatingsystem | sort count -Descending 
#
#$var = $var.Group

# SQL
#-- USE [SCCM DATABASE]
#GO
#
#/****** Object:  Table [dbo].[ADComputerInformation]    Script Date: 07/29/2011 09:09:15 ******/
#SET ANSI_NULLS ON
#GO
#
#SET QUOTED_IDENTIFIER ON
#GO
#
#CREATE TABLE [dbo].[ADComputerInformation](
#	[OperatingSystem] [nvarchar](256) NULL,
#	[ParentContainerDN] [nvarchar](max) NULL,
#	[Name] [nvarchar](256) NULL,
#	[Domain] [nvarchar](256) NULL
#) ON [PRIMARY]
#
#GO
Get-ADOUInfo
Import-Module [DIRECTORY WHERE THE SCCM MODULE IS]\SCCM-Commands.psm1'
add-pssnapin Quest.ActiveRoles.ADManagement
Set-QADPSSnapinSettings -DefaultSizeLimit 100000
$sccm = Connect-SCCMServer
$var = Get-QADObject -Type 'organizationalUnit' | select DN, Description, ParentContainerDN
$conn = New-Object System.Data.SqlClient.SqlConnection("Data Source=[SQL Servers]; Initial Catalog=[SCCM Database]; Integrated Security=SSPI")
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = "delete from ADOUInformation"
$cmd.ExecuteNonQuery()
foreach ($i in $var) {
	if ($i.Description -eq $Null) {$description = $Null}
	else {$description = $i.Description.ToString()}
	if ($i.DN -eq $null) {$DN = $null}
	else {$DN = $i.DN.ToString()}
	if ($i.ParentcontainerDN -eq $null) {$ParentContainerDN = $Null}
	else {$ParentContainerDN = $i.ParentContainerDN.tostring()}
$cmd.CommandText ="INSERT ADOUInformation VALUES ('$DN','$description', '$ParentContainerDN' )"
$cmd.ExecuteNonQuery()
}
$conn.Close()

#SQL
#
#-- USE [SCCM DATABASE]
#GO
#
#/****** Object:  Table [dbo].[ADOUInformation]    Script Date: 07/29/2011 10:44:46 ******/
#SET ANSI_NULLS ON
#GO
#
#SET QUOTED_IDENTIFIER ON
#GO
#
#CREATE TABLE [dbo].[ADOUInformation](
#	[DN] [nvarchar](max) NULL,
#	[Description] [nvarchar](256) NULL,
#	[ParentContainerDN] [nvarchar] (max) NULL
#) ON [PRIMARY]
#
#GO


Get-ADSiteInfo
Import-Module [DIRECTORY WHERE THE SCCM MODULE IS]\SCCM-Commands.psm1'
add-pssnapin Quest.ActiveRoles.ADManagement
Set-QADPSSnapinSettings -DefaultSizeLimit 100000
$sccm = Connect-SCCMServer
$var = Get-QADObject -Searchroot "CN=Subnets,CN=Sites,CN=Configuration,DC=[FOREST ROOT],DC=[FOREST ROOT]" -includedproperties "Description", "Location" | select description, location
$conn = New-Object System.Data.SqlClient.SqlConnection("Data Source=[SCCM SERVER]; Initial Catalog=[SCCM Database]; Integrated Security=SSPI")
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = "delete from ADSiteInformation"
$cmd.ExecuteNonQuery()
foreach ($i in $var) {
	if ($i.Description -eq $null) {$description = $null}
	else {$description = $i.Description.ToString()}
	if ($i.location -eq $null) {$location = $null}
	else {$location = $i.location.ToString()}
$cmd = $conn.CreateCommand()
$cmd.CommandText ="INSERT ADSiteInformation VALUES ('$location','$description' )"
$cmd.ExecuteNonQuery()
}
$conn.Close()

#SQL Code
#
#-- USE [SCCM DATABASE]
#GO
#
#/****** Object:  Table [dbo].[ADSiteInformation]    Script Date: 07/28/2011 13:18:14 ******/
#SET ANSI_NULLS ON
#GO
#
#SET QUOTED_IDENTIFIER ON
#GO
#
#CREATE TABLE [dbo].[ADSiteInformation](
#	[LocationCode] [nchar](10) NULL,
#	[LocationDescription] [nvarchar](max) NULL
#) ON [PRIMARY]
#
#GO

Delete-SCCMCollectionRule : Addition to the SCCM-Commands.psm1

Did you know that there is SCCM-Commands powershell module?  You can get them here:  http://www.snowland.se/sccm-posh/

Today I had a need to delete a collection member.  But there isn’t a function in the SCCM-Commands powershell module that can do that.

Well, there is now!!  Just copy this function into the SCCM-Commands.psm1 file

 

Function Delete-SCCMCollectionRule { 

    [CmdletBinding()] 

    PARAM ( 

        [Parameter(Mandatory=$true,  HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer, 

        [Parameter(Mandatory=$true,  HelpMessage="CollectionID", ValueFromPipelineByPropertyName=$true)] $collectionID, 

        [Parameter(Mandatory=$true,  HelpMessage="Rule Name", ValueFromPipeline=$true)] [String] $queryRuleName

                ) 

  

    PROCESS { 

                #$CollectionID = CEN00247

                #$SCCMServer = $SCCM

                #$QueryRuleName = LWMN00x2

        $coll = [wmi]"$($SccmServer.SccmProvider.NamespacePath):SMS_Collection.CollectionID='$collectionID'"

             # Find each computer 

                        foreach ($i in $queryRuleName) 

                            {$computer = Get-SCCMComputer -sccmServer $SccmServer -NetbiosName $i

                   # See if the computer is already a member 

                $found = $false

                if ($coll.CollectionRules -ne $null) { 

                foreach ($member in $coll.CollectionRules)

                                    { 

                  if ($member.ResourceID -eq $computer.ResourceID) {$found = $true} 

                    } 

                } 

            if ($found) { 

                                $ResourceToDelete = $coll.CollectionRules | where {$_.rulename -eq $i}

                                $coll.DeleteMembershipRule($ResourceToDelete)

                           } else { 

                Write-Verbose "Computer $queryRuleName is not in the collection"

                    }     

                    } 

    } 

        }

Yeah, I am pretty much the best thing since sliced bread!

Bravo! IT colleague takes the initiative to launch a money-saving training program
July 06, 2011

MatthewTeegarden
Photo: Matthew Teegarden of Information Technology is passionate about training and finding cost-effective solutions.

Matthew Teegarden isn’t one to sit idle when something needs to get done, so when he heard Information Technology colleagues cite concerns about the availability of training, he took things into his own hands. When Matthew learned that the Kenexa employee engagement survey rated “Career Development and Training” as an area where improvements could be made, he decided to step up to the plate with a solution.

“During Steve Brown’s Town Hall and Joe Terhaar’s Town Hall, I heard similar statements regarding training and opportunities for career growth at Carlson,” Matthew explained. “I felt that my skills could be very useful, and the monetary savings could be huge. I heard employees feel there isn’t an opportunity for training. Well, there is now! I am passionate about training my fellow coworkers and training, in general.”

Before joining Carlson, Matthew was an educator at Benchmark Learning for five years. He worked out an agreement with his former employer to provide training materials, virtual classroom facilities and machines. His initiative resulted in a training program in Powershell, a technical scripting language. So far, 17 IT professionals have completed Matthew’s Powershell training course.

“It appears to be a very cost effective training solution that also addresses issues from the Kenexa survey,” said Chris Schouviller, team lead, Operations & Infrastructure, who is Matthew’s manager.
“We set up a Sharepoint site for users of the class to exchange ideas, comment on the class and suggest future classes,” explained Chris. “This allows staff to keep what they have learned fresh and relevant. Matthew works with them to bring in real-world Carlson problems and opportunities and address these with Powershell.”

IT worked with the Human Resources team to include the class in the Carlson Learning Network, giving staff credit for taking the course.

“Matthew already has a full-time job, but he took the time to build the training,” says Joe Terhaar, vice president, Information Technology, Carlson. “This is the kind of initiative you like to see in people.”

Matthew’s win-win proposal not only provides Carlson IT with the skills needed to be innovative and cutting edge, but also provides excellent return on investment! Bravo, Matthew!

SCUP 2011

SCUP (System Center Updates Publisher) 2011 is out.  You can download it here:  http://technet.microsoft.com/en-us/systemcenter/bb741049

There is a TON of information on the above link.  However, you do need to configure a few things that you need to do in Group Policy.

First you need to configure a GPO with the following:  Computer > Administrative Templates > Windows Components > Windows Update | Allow Signed Content from intranet Microsoft Update services location.  (NOTE:  No other settings should be configured under Windows Update)

Then you need to install the SCUP certificates on the SCCM Site Server that is running SCUP.  I chose to have SCUP issue a self signed cert.  That cert is placed in Computer > WSUS > Certificates store.  Export it to a location.  Then you need to import it into the Computer > Trusted Publishers and Computer > Trusted Root Authorities stores. 

Finally you need distribute the SCUP Self Signed cert to all the computers that you want to participate in SCUP deployments.  To do this create a new GPO. 

  • To place the key in Computer > Trusted Root Authority store – Go to Computer Configuration > Windows Settings > Security Settings > Public Key Policies > Trusted Root Certificate Authorities.  Right click Trusted Root Certificate Authorities and select import.  Browse to the location where you exported the SCUP self signed cert.
  • To place the key in the Computer > Trusted Publishers store – Go to Computer Configuration > Windows Settings > Security Settings > Software Restriction Policies.  Right click Software Restriction Policies and select New Software Restriction Policies.  Now navigate to Additional Rules.  Right click Additional Rules and select New Certificate Rule.  Browse to the location where you exported the SCUP self signed cert.  Change the drop down to Unrestricted.

You can now deploy SCUP publications like Adobe patches!

PowerShell–Running a script from a bat file

You may have a need to run a PS1 script at a certain schedule.  The easiest way to do this is using a bat file.

However, your systems (servers at least!) should have the ExecutionPolicy set to Default which means that no scripts will run.  Bellow is an example of using a bat file to change the ExecutionPolicy just for the current scope that PowerShell.exe is running in and launch the PS1 script.  Once the script is done running, the session is closed and your Execution Policy is still set to Default.

PowerShell.exe -command Set-ExecutionPolicy RemoteSigned -scope Process; C:\PowershellScripts\YourScript.ps1

PowerShell–Displaying information from Hash Tables

Let’s say you have a CSV file that you want to import into a hash table.  The contents of the CSV file are as follows:

srv, os

DC1, Windows Server 2008R2Full

Win7, Windows 7

The following code shows the incorrect display as well as the correct one.

# Create a hash table
$HT = @{}
# Import a CSV into the hash table
Import-CSV [your csv file] | foreach {$ht.add($_.”srv”, $_.”os”)}
# The following command doesn’t work
foreach ($i in $ht) {Write-Host $i.values}
# Try this instead
foreach ($i in k$ht.keys) {Write-Host $i “is running” $ht.$i}

PowerShell–AD Module

Searching AD for a specific attribute using the get-aduser function from the ActiveDirectory PowerShell module.

First the ActiveDirectory module needs to be loaded.  You can check to see if the module is available by typing:

Get-Module -listavailable

If you don’t see the ActiveDirectory module you will need to download the RSAT. 

To load the module type:

Import-Module ActiveDirectory

Search for a specific attribute:

# Searches the entire directory
Get-ADUser -Filter * -Properties * | Where {$_.physicalDeliveryOfficeName -ne "Whatever"

# To set all users who don't have "Whatever" in their Office attribute
Get-ADUser -Filter * -Properties * | Where {$_.physicalDeliveryOfficeName -ne "Whatever" | set-aduser -office "SomethingElse"

PowerShell–Get ACL with a readable path

When you want to run the Get-Acl cmdlet against a directory, the path property in the results is a bit messy.  Example:

Get-Childitem C:\Labs -recurse | Get-Acl | Format List Path, Owner, Access

We can clean up the Path property by using a custom label with a substring

Get-Childitem C:\Labs -recurse | Get-Acl | Format List @{Label="ShortPath"; Expression= {$_.Path.Substring(38)}},Path, Owner, Access

Powershell–Comparing processes

This week I am teaching a PowerShell v2.0 class at Benchmark Learning.  During the class I usually show the following demo.

# Get the current running processes
$Baseline = Get-Process
# Create some new processes
Calc
Notepad
# Capture the current running processes
$Now = Get-Process
# Compare the two variables
Compare-Object $Baseline $Now

The students thought that was pretty cool, but wanted to know how to kill the processes that were not in the $Baseline variable.  Well, here you go…

# Get the current running processes
$Baseline = Get-Process
# Create some new processes
Calc
Notepad
# Capture the current running processes
$Now = Get-Process
# Compare the two variables
$Processes = Compare-Object $Baseline $Now
foreach ($i in $processes) {$i.inputObject | Stop-Process}