<# ##################################################################### SCRIPT Get-PCsUserProfileInfo.ps1 SYNTAX .\Get-PCUserProfilePath.ps1 -srcDomainPath -IterationDelay -ComputerType -ComputerListFile -srcDomainPath This is not required. If not specified the script will query from the root of the domain from which the user is running the script in (i.e. script users Domain). The Path can be in the syntax of DNS fqdn ("domain1.com" or LDAP dn (DC=domain1,DC=com) except when querying a specific OU structure, when the syntax of this parameter must be in LDAP dn form only e.g. "OU=UK,DC=domain1,DC=com" -IterationDelay This is not required. It provides an option in seconds to specifiy the time between each computer query. -ComputerType This is not required. Only works when the -ComputerListFile parameter is not used and needs to be followed by the word server or desktop to target the script at either of these types of systems. -ComputerListFile This is not required. Provides an option to provide a list of computers to run the script against. It will still get data on the computer object from AD. SYNOPSIS Gets all workstation or server Computers from the domain from which the script is run in (i.e. the domain you are logged in to) or the AD location specified. Then uses the computer list to query each computers to determine the users that have used the computer and various relavant data. The Script creates 3 files in the same location the script is run from (ComputerUserReport.csv, ComputerProfileReport and FailedComputers.csv). This script ustilises 3 functions provided by an external source Information for these functions is provided at the head of each function. NOTE Script requires no parameters or arguments, but does have some. I recommend you have the relevant permissions in the domain and on the computers being queried for optimal results. This script is provided "AS IS" with no warranties, confers no rights and is not supported by the authors or the authors employer. Use of this script sample is subject to the terms specified at http://www.microsoft.com/info/copyright.mspx. AUTHOR Carl Harrison VERSION: 1.0 - First cut ##################################################################### #> #These are the script Pararmeters. If not sepcified at command line the script runs with default settings Param ([Parameter()][string]$srcDomainPath='', [Parameter()][string]$IterationDelay='0', [Parameter()][string]$ComputerType="desktop", [Parameter()][string]$ComputerListFile='') Function Get-RemoteRegistry #.SYNOPSIS # Query the registry on a remote machine #.NOTE # You have to have access, and the remote registry service has to be running # # Version History: # 3.0 # + updated to PowerShell 2 # + support pipeline parameter for path # 2.1 # + Fixed a pasting bug # + I added the "Properties" parameter so you can select specific redgistry values # ###################################################################################### #.EXAMPLE # (Get-RemoteRegistry ${Env:ComputerName} HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\).Subkeys | # Get-RemoteRegistry ${Env:ComputerName} ` # -Path { "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$_" } ` # -Properties DisplayName, DisplayVersion, Publisher, InstallDate, HelpLink, UninstallString #.EXAMPLE # Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP" # * Returns a list of subkeys (because this key has no properties) #.EXAMPLE # Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" # * Returns a list of subkeys and all the other "properties" of the key #.EXAMPLE # Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727\Version" # * Returns JUST the full version of the .Net SP2 as a STRING (to preserve prior behavior) #.EXAMPLE # Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" Version # * Returns a custom object with the property "Version" = "2.0.50727.3053" (your version) #.EXAMPLE # Get-RemoteRegistry $RemotePC "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727" Version,SP # * Returns a custom object with "Version" and "SP" (Service Pack) properties # # For fun, get all .Net Framework versions (2.0 and greater) # and return version + service pack with this one command line: # # Get-RemoteRegistry $RemotePC "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP" | # Select -Expand Subkeys | ForEach-Object { # Get-RemoteRegistry $RemotePC "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\$_" Version,SP { param( [Parameter(Position=0, Mandatory=$true)] [string]$computer = $(Read-Host "Remote Computer Name") , [Parameter(Position=1, ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true, Mandatory=$true)] [string]$Path = $(Read-Host "Remote Registry Path (must start with HKLM,HKCU,etc)") , [Parameter(Position=2)] [string[]]$Properties ) process { $root, $last = $Path.Split("\") $last = $last[-1] $Path = $Path.Substring($root.Length + 1,$Path.Length - ( $last.Length + $root.Length + 2)) $root = $root.TrimEnd(":") #split the path to get a list of subkeys that we will need to access # ClassesRoot, CurrentUser, LocalMachine, Users, PerformanceData, CurrentConfig, DynData switch($root) { "HKCR" { $root = "ClassesRoot"} "HKCU" { $root = "CurrentUser" } "HKLM" { $root = "LocalMachine" } "HKU" { $root = "Users" } "HKPD" { $root = "PerformanceData"} "HKCC" { $root = "CurrentConfig"} "HKDD" { $root = "DynData"} default { return "Path argument is not valid" } } #Access Remote Registry Key using the static OpenRemoteBaseKey method. Write-Verbose "Accessing $root from $computer" $rootkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($root,$computer) if(-not $rootkey) { Write-Error "Can't open the remote $root registry hive" } Write-Verbose "Opening $Path" $key = $rootkey.OpenSubKey( $Path ) if(-not $key) { Write-Error "Can't open $($root + '\' + $Path) on $computer" } $subkey = $key.OpenSubKey( $last ) $output = new-object object if($subkey -and $Properties -and $Properties.Count) { foreach($property in $Properties) { Add-Member -InputObject $output -Type NoteProperty -Name $property -Value $subkey.GetValue($property) } Write-Output $output } elseif($subkey) { Add-Member -InputObject $output -Type NoteProperty -Name "Subkeys" -Value @($subkey.GetSubKeyNames()) foreach($property in $subkey.GetValueNames()) { Add-Member -InputObject $output -Type NoteProperty -Name $property -Value $subkey.GetValue($property) } Write-Output $output } else { $key.GetValue($last) } } }#END Get-RemoteRegistry Function function Get-Profiles { <# .Synopsis Gets a list of user profiles on a computer. .Description This command gets a list of user profiles on a computer. The info is pipable and can be used to do other useful tasks. .Parameter computer The computer on which you wish to recieve profiles. (defaults to localhost) .Example Get-Profiles -comptuer Server01 Gets all of the profiles from Server01 .Example Get-Content .\computers.txt | Get-Profiles Returns all of the profiles for the computers listed in computers.txt .Link Remove-Profiles Author: Scott Keiffer Date: 08/27/09 Website: http://www.cm.utexas.edu #Requires -Version 2.0 #> param ([parameter(ValueFromPipeLine=$true)][String]$computer = "localhost") process { $ErrorActionPreference = "SilentlyContinue" # Check for pipe input if ($_.Name) { $computer = $_.Name | Test-Host -TCPPort 135 } elseif ($_) { $computer = $_ | Test-Host -TCPPort 135 } else { $computer = Test-Host -TCPPort 135 $computer } $profiles=$null # Get the userprofile list and then filter out the built-in accounts if ($computer) { $profiles = Get-WmiObject win32_userprofile -filter "Special<>'True'" -computerName $computer | ?{$_.SID -like "s-1-5-21*"} if (!$?) { Write-Warning "Unable to communicate with - $computer"; continue } } else { Write-Warning "Unable to communicate with specified host."; continue } if($profiles.count -gt 0 -or ($profiles -and ($profiles.GetType()).Name -eq "ManagementObject")) { # Loop through the list of profiles foreach ($profile in $profiles) { Write-Verbose ("Reading profile for SID " + $profile.SID + " on $computer") $user = $null $objUser = $null #Create output objects $Output = New-Object PSObject # create a new secuity identifier object $ObjSID = New-Object System.Security.Principal.SecurityIdentifier($profile.SID) # Try to link the user SID to an actual user object (can fail for local accounts on remote machines, # or the user no long exists but the profile still remains) Try { $objUser = $objSID.Translate([System.Security.Principal.NTAccount]) } catch { $user = "ERROR: Not Readable" } if ($objUser.Value) { $user = $objUser.Value } $LastUsed = $Profile.ConvertToDateTime($Profile.Lastusetime) $Output | Add-Member NoteProperty Computer $computer $Output | Add-Member NoteProperty Username $user $Output | Add-Member NoteProperty ProfileRef $profile $Output | Add-Member NoteProperty LastUsed $LastUsed Write-Output $Output } } } }#END Get-Profiles Function function Test-Host { <# .Synopsis Test a host for connectivity using either WMI ping or TCP port .Description Allows you to test a host for connectivity before further processing .Parameter Server Name of the Server to Process. .Parameter TCPPort TCP Port to connect to. (default 135) .Parameter Timeout Timeout for the TCP connection (default 1 sec) .Parameter Property Name of the Property that contains the value to test. .Example # To test a list of hosts. cat ServerFile.txt | Test-Host | Invoke-DoSomething .Example # To test a list of hosts against port 80. cat ServerFile.txt | Test-Host -tcp 80 | Invoke-DoSomething .Example # To test the output of Get-ADComputer using the dnshostname property Get-ADComputer | Test-Host -property dnsHostname | Invoke-DoSomething .OUTPUTS Object .INPUTS object .Link N/A NAME: Test-Host AUTHOR: YetiCentral\bshell Website: www.bsonposh.com LASTEDIT: 02/04/2009 18:25:15 #Requires -Version 2.0 #> [CmdletBinding()] Param( [Parameter(ValueFromPipeline=$true,Mandatory=$True)] $ComputerName, [Parameter()] [int]$TCPPort, [Parameter()] [int]$timeout=500, [Parameter()] [string]$property ) Begin { function TestPort { Param($srv,$tport,$tmOut) Write-Verbose " [TestPort] :: Start" Write-Verbose " [TestPort] :: Setting Error state = 0" $ErrorActionPreference = "SilentlyContinue" Write-Verbose " [TestPort] :: Creating [system.Net.Sockets.TcpClient] instance" $tcpclient = New-Object system.Net.Sockets.TcpClient Write-Verbose " [TestPort] :: Calling BeginConnect($srv,$tport,$null,$null)" $iar = $tcpclient.BeginConnect($srv,$tport,$null,$null) Write-Verbose " [TestPort] :: Waiting for timeout [$timeout]" $wait = $iar.AsyncWaitHandle.WaitOne($tmOut,$false) # Traps trap { Write-Verbose " [TestPort] :: General Exception" Write-Verbose " [TestPort] :: End" return $false } trap [System.Net.Sockets.SocketException] { Write-Verbose " [TestPort] :: Exception: $($_.exception.message)" Write-Verbose " [TestPort] :: End" return $false } if(!$wait) { $tcpclient.Close() Write-Verbose " [TestPort] :: Connection Timeout" Write-Verbose " [TestPort] :: End" return $false } else { Write-Verbose " [TestPort] :: Closing TCP Sockett" $tcpclient.EndConnect($iar) | out-Null $tcpclient.Close() } if($?){Write-Verbose " [TestPort] :: End";return $true} } function PingServer { Param($MyHost) Write-Verbose " [PingServer] :: Pinging $MyHost" $pingresult = Get-WmiObject win32_pingstatus -f "address='$MyHost'" Write-Verbose " [PingServer] :: Ping returned $($pingresult.statuscode)" if($pingresult.statuscode -eq 0) {$true} else {$false} } } Process { Write-Verbose "" Write-Verbose " Server : $ComputerName" if($TCPPort) { Write-Verbose " Timeout : $timeout" Write-Verbose " Port : $TCPPort" if($property) { Write-Verbose " Property : $Property" if(TestPort $ComputerName.$property -tport $TCPPort -tmOut $timeout){$ComputerName} } else { if(TestPort $ComputerName -tport $TCPPort -tmOut $timeout){$ComputerName} } } else { if($property) { Write-Verbose " Property : $Property" if(PingServer $ComputerName.$property){$ComputerName} } else { Write-Verbose " Simple Ping" if(PingServer $ComputerName){$ComputerName} } } Write-Verbose "" } }#END Test-Host FUNCTION #THIS IS THE START OF THE SCRIPT #Define Some variables and Arrays #These are used in the Directory Search filter $strOperatingSystem1 = "*XP*" $strOperatingSystem2 = "*Vista*" $strOperatingSystem3 = "*Windows 7*" $strOperatingSystem4 = "*Server*" $Computers = @() $FailedComputerReport = @() $ComputerUserReport = @() $ComputerProfilesReport = @() If(!($ComputerListFile)) { #Set up the Directory Search parameters if(!($srcDomainPath)) { $objDomain = New-Object System.DirectoryServices.DirectoryEntry } Else { $objdomain = new-object DirectoryServices.DirectoryEntry ("LDAP://$srcDomainPath") } $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearcher.SearchRoot = $objDomain If ($ComputerType -eq "desktop") { $objSearcher.Filter = "(&(ObjectCategory=Computer)(|(OperatingSystem=$strOperatingSystem1)(OperatingSystem=$strOperatingSystem2)(OperatingSystem=$strOperatingSystem3)))" } ElseIf ($ComputerType -eq "server") { $objSearcher.Filter = "(&(ObjectCategory=Computer)(OperatingSystem=$strOperatingSystem4))" } Else { Write-Host "Invalid Computer Type specified, Exiting..." ; Sleep 5 ; Exit } $objSearcher.PageSize = 1000 $objSearcher.SearchScope = "Subtree" #Define the Attributes we need from the search $colProplist = "name","dnshostname","operatingsystem" #Do the AD Search foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)|out-null} $colResults = $objSearcher.FindAll() } Else { #Verfiy the path to the Input file if (!(Test-Path $ComputerListFile)) {Write-Host "Your Input File does not exist, Exiting..." ; Sleep 5 ; Exit} $ComputerList = Get-Content $ComputerListFile #Set up the Directory Search parameters if(!($srcDomainPath)) { $objDomain = New-Object System.DirectoryServices.DirectoryEntry } Else { $objdomain = new-object DirectoryServices.DirectoryEntry ("LDAP://$srcDomainPath") } $colResults = @() $objsearcher = new-object DirectoryServices.DirectorySearcher($objdomain) foreach ($j in $colPropList){$objSearcher.PropertiesToLoad.Add($j)|out-null} #Define the Attributes we need from the search $colProplist = "name","dnshostname","operatingsystem" $DomainName = $objDomain.distinguishedname ForEach ($CompName in $ComputerList) { $ErrorComputer = $Null #Set the Search Filter $objsearcher.filter = "(&(objectCategory=Computer)(cn=$CompName))" #Do the AD Search $ErrorCondition = $False Try { $ADCompObject = $objsearcher.findone().getdirectoryentry() } Catch { Write-Host "$CompName does not exist in $DomainName" $ErrorComputer = New-Object PSObject $ErrorComputer | Add-Member NoteProperty Computer "$CompName" $ErrorComputer | Add-Member NoteProperty Name "Computer does not exist in $DomainName" $FailedComputerReport += $ErrorComputer $ErrorCondition = $True } If (!($ErrorCondition)) { #The computer exists in AD so add it to the collection for the rest of the script $colResults += $ADCompObject #write-host $ADCompObject.name } else { $ADCompObject = $Null } } } If ($colResults.Count -gt 0) { #Set up the ping variable $Ping = New-Object System.Net.NetworkInformation.Ping #Now lets go and do something with the list of computers we have ForEach ($objResult in $colResults) { Start-Sleep -s $IterationDelay $Computer = $objResult.Properties.name $ComputerDNS = $objResult.Properties.dnshostname $ComputerOS = $objResult.Properties.operatingsystem #Write-Host "Pinging $ComputerDNS" $errorActionPreference="SilentlyContinue" $Pingy = $ping.send($ComputerDNS,1000) $NotXPComputerUserOutput = $Null $XPComputerUserOutput = $Null $XPLastLoggedOnUser = $Null $NotXPLastLoggedOnUser = $Null $XPDomainandUser = $Null $FailedPing = $Null $ErrorComputer = $Null $NotXPComputerUserProfileOutput = $Null Try { #This is where we go get the information If ($Pingy.status.tostring() –eq “Success”) { #Cool we can get to the Computer now lets gather some information write-host “$Computer Available” $errorActionPreference="Continue" #We do this differently for WIndows XP\2003 because Win32_UserProfile is not on these systems #Also LastLoggedOnUser doesn't exist on XP\2003 If ($ComputerOS -match "XP" -or $ComputerOS -match "2003") { #Get the details of the user that was last logged on from the registry and convert to single string then save it to array $XPLastLoggedOnUser = Get-RemoteRegistry $ComputerDNS "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" DefaultUserName,DefaultDomainName $XPDomainandUser = [String] $XPLastLoggedOnUser.DefaultDomainName + "\" + $XPLastLoggedOnUser.DefaultUserName $XPComputerUserOutput = New-Object PSObject $XPComputerUserOutput | Add-Member NoteProperty Computer "$ComputerDNS" $XPComputerUserOutput | Add-Member NoteProperty LastLoggedOnUser $XPDomainandUser $ComputerUserReport += $XPComputerUserOutput #Go get the list of Profiles from the registry, but only Domain based profiles $XPComputerProfileList = (Get-RemoteRegistry $ComputerDNS "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\").Subkeys | ?{$_ -like "s-1-5-21*"} #Process each of the profiles #$XPComputerProfileList | ft ForEach ($XPComputerProfile in $XPComputerProfileList) { $XPComputerUserProfileOutput = $Null $ObjUserProfile = $Null $UserName = $Null $ObjSID = $Null $XPComputerProfilePath = $Null $ThisDrive = $Null $ThisProfilePath =$Null $Query = $Null $LastUsed = $Null $NTUSERFile = $Null $XPComputerProfilePath = Get-RemoteRegistry $ComputerDNS "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$XPComputerProfile" ProfileImagePath #Write-Host $XPComputerProfilePath.ProfileImagePath $ThisDrive = ($XPComputerProfilePath.ProfileImagePath).SubString(0,2) $ThisProfilePath = ($XPComputerProfilePath.ProfileImagePath).TrimStart($ThisDrive).Replace("\","\\") + "\\" #Query to get file data back from remote computer $query = "SELECT * FROM CIM_DataFile WHERE Drive='" + $ThisDrive + "' AND Path='" + $ThisProfilePath + "' AND FileName='NTUSER' AND Extension='dat'" #Write-Host $query #Run the WMI query then get the LastModified date and time $NTUSERFile = Get-WmiObject -computer $ComputerDNS -Query $query $LastUsed = $NTUSERFile.ConvertToDateTime($NTUSERFile.LastModified) #$ObjSID = $XPComputerProfile.Value # Try to link the user SID to an actual user object (can fail for local accounts on remote machines, # or the user no long exists but the profile still remains) $ObjSID = New-Object System.Security.Principal.SecurityIdentifier($XPComputerProfile) Try { $ObjUserProfile = $ObjSID.Translate([System.Security.Principal.NTAccount]) } catch { $UserName = "ERROR: Not Readable" } if ($ObjUserProfile.Value) { $UserName = $objUserProfile.Value } #Save all the XP Profile data to an array $XPComputerUserProfileOutput = New-Object PSObject $XPComputerUserProfileOutput | Add-Member NoteProperty Computer "$ComputerDNS" $XPComputerUserProfileOutput | Add-Member NoteProperty Username "$Username" $XPComputerUserProfileOutput | Add-Member NoteProperty ProfileRef "$ObjSID" $XPComputerUserProfileOutput | Add-Member NoteProperty LastUsed "$LastUsed" $ComputerProfilesReport += $XPComputerUserProfileOutput } } else { #On newer systems Vista\Win7\W2k(R2) it is all so much easier #Get the LastLoggedOnUser registry value then save it to an array $NotXPLastLoggedOnUser = Get-RemoteRegistry $ComputerDNS "HKLM\Software\Microsoft\Windows\CurrentVersion\Authentication\LogonUI" LastLoggedOnUser $NotXPComputerUserOutput = New-Object PSObject $NotXPComputerUserOutput | Add-Member NoteProperty Computer "$ComputerDNS" $NotXPComputerUserOutput | Add-Member NoteProperty LastLoggedOnUser $NotXPLastLoggedOnUser.LastLoggedOnUser $ComputerUserReport += $NotXPComputerUserOutput #This calls the function that gets the profile data, ok so its all wrapped up in a function which makes it look a lot easier $NotXPComputerUserProfileOutput = Get-Profiles -computer $ComputerDNS $ComputerProfilesReport += $NotXPComputerUserProfileOutput } } else { #uh-oh we cannot get to the current Computer write-host “$Computer Not Pingable" $FailedPing = New-Object PSObject $FailedPing | Add-Member NoteProperty Computer "$Computer" $FailedPing | Add-Member NoteProperty Name "Computer Not Pingable" $FailedComputerReport += $FailedPing } } Catch { #And this just catches any weird error events usually when we can ping but have no Access Rights #Or the script has barfed write-host “$Computer Unknown Error" $ErrorComputer = New-Object PSObject $ErrorComputer | Add-Member NoteProperty Computer "$Computer" $ErrorComputer | Add-Member NoteProperty Name "Computer Unknown Error" $FailedComputerReport += $ErrorComputer } Finally { #A bit of a tidy up $errorActionPreference="Continue" $pingy = $null $Computer = $Null $ComputerDNS = $Null $ComputerOS = $Null $ErrorComputer = $Null } } #Now we have the data let's save it to file - in the same location the script is run from $DateTime = (Get-Date).toshortdatestring().replace("/","") $DateTime += (Get-Date).toshorttimestring().replace(":","") $ComputerUserReport | Export-Csv -notypeinformation ".\ComputerUserReport_$DateTime.csv" $ComputerUserReport = @() $ComputerProfilesReport | Export-Csv -notypeinformation ".\ComputerProfileReport_$DateTime.csv" $ComputerProfilesReport = @() $FailedComputerReport | Export-Csv -notypeinformation ".\FailedComputerReport_$DateTime.csv" $FailedComputerReport = @() } Else { Write-Host "Nothing Found" }