Obtaining information from user profiles

by rambog at 2012-09-06 15:31:46

I am trying to extract the users who have accessed their profiles over the last 30 days. This lets me know who used these particular workstations over the past month. As a basis, I found the following:
http://blogs.technet.com/b/heyscripting … puter.aspx

A couple of items:
1. I cannot get at the lastusetime property for each profile. This is key to being about to gather just the profiles that have been accessed. Is there another way to get this property or shoud I go in some roundabout fashion of using the profileimagepath and then checking the last modified date on that file folder?
2. My attempt at GWMI for the profile is not working (" Get-WmiObject win32_userprofile… "). I receive the following error
Get-WmiObject : Cannot validate argument on parameter ‘ComputerName’. The argum
ent is null or empty. Supply an argument that is not null or empty and then try
the command again.
At C:\scripts\ComputerProfile.ps1:23 char:44
+ Get-WmiObject win32_userprofile -Computer <<<< $CompName |select @{L
ABEL="last used";EXPRESSION={$.ConvertToDateTime($.lastusetime)}},LocalPath,
SID | ft -a |Out-Host
+ CategoryInfo : InvalidData: (:slight_smile: [Get-WmiObject], ParameterBindi
ngValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Power
Shell.Commands.GetWmiObjectCommand

The program, in its infancy, is as follows:
$ObjFilter = "(objectClass=Computer)"
$objSearch = New-Object System.DirectoryServices.DirectorySearcher
$objSearch.PageSize = 20000
$objSearch.Filter = $ObjFilter
$objSearch.SearchRoot = "LDAP://OU=Workstations,DC=labdomain,DC=org"
$AllObj = $objSearch.FindAll()
foreach ($Obj in $AllObj)
{
$keytype=[Microsoft.Win32.RegistryHive]::LocalMachine
$remotekey=[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype,$CName)
$regkey=$remotekey.OpenSubKey("Software\Microsoft\Windows NT\CurrentVersion\ProfileList")
#$profileList = ‘HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList’
Get-WmiObject win32_userprofile -Computer $CompName |select @{LABEL="last used";EXPRESSION={$.ConvertToDateTime($.lastusetime)}},LocalPath, SID | ft -a |Out-Host
Get-childItem ‘HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList’ | % {Get-ItemProperty $.pspath } | Select profileImagePath, sid ,lastusetime|Out-Host
}


I know I have quite a ways to go; any direction you could provide would be most appreciative.
by DonJ at 2012-09-06 18:29:28
Regarding the LastUseTime property - what do you mean, "cannot get at?" Do you get an error, or is the property simply blank? All the machines I have access to are populating that property with a standard WMI-format datetime.

Regarding your attempt at GWMI, it would appear that $CompName is empty, which is why you’re getting the error. Looking at your script, I don’t see where $CompName is being populated.

If $Obj contains an AD computer object, then inside your foreach loop you would need to add something like $CompName = $Obj.Name, I’m guessing.
by rambog at 2012-09-07 06:41:35
With respect to the LastUseTime in Get-ChildItem, the column displaying the output is blank.

With respect to GWMI, the $CompName variable was a fossil of some steps previously taken (and omitted) in trying to input the computer in the foreach loop. I was having problems converting this directorysearch type into an object which I can then extract properties.

When I put in $Obj after -Computer switch, I get the following (despite being able to connect to these computers in a different script and get WMI properties)
Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x80070
6BA)
At C:\scripts\ComputerProfile.ps1:23 char:16
+ Get-WmiObject <<<< win32_userprofile -Computer $Obj |select @{LABEL=
"last used";EXPRESSION={$
.ConvertToDateTime($.lastusetime)}},LocalPath, SID |
ft -a |Out-Host
+ CategoryInfo : InvalidOperation: (:slight_smile: [Get-WmiObject], COMExcept
ion
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands
.GetWmiObjectCommand


When I put in $Obj.Name as the parameter following -Computer,

Get-WmiObject : Cannot validate argument on parameter ‘ComputerName’. The argum
ent is null or empty. Supply an argument that is not null or empty and then try
the command again.
At C:\scripts\ComputerProfile.ps1:23 char:44
+ Get-WmiObject win32_userprofile -Computer <<<< $Obj.Name |select @{L
ABEL="last used";EXPRESSION={$
.ConvertToDateTime($_.lastusetime)}},LocalPath,
SID | ft -a |Out-Host
+ CategoryInfo : InvalidData: (:slight_smile: [Get-WmiObject], ParameterBindi
ngValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.Power
Shell.Commands.GetWmiObjectCommand
by coderaven at 2012-09-07 06:50:42
Since your using ADSI getting the name is a little different.

Try using this: $obj.Properties["name"]
by rambog at 2012-09-07 12:34:34
Here is the code after pirating from another forum entry,

#Script created to gather profiles from PCs while searching the domain tree, 9-7-12
#based upon entry from PoSherLife, http://www.powershellcommunity.org/Foru … fault.aspx
$ComputerInfoFile = New-Item -type file -force "C:\Vendor\ComputerReports\ProfileInfo.txt"
#"ComputerNametProfiletLastLogon" | Out-File $ComputerInfoFile -encoding ASCII
$ObjFilter = "(objectClass=Computer)"
$objSearch = New-Object System.DirectoryServices.DirectorySearcher
$objSearch.PageSize = 20000
$objSearch.Filter = $ObjFilter
$objSearch.SearchRoot = "LDAP://OU=Sample,OU=Workstations,DC=labdomain,DC=org"
$AllObj = $objSearch.FindAll()
foreach ($Obj in $AllObj)
{
$CompName=$Obj.Properties
$CName=$CompName.name
$ping=new-object System.Net.NetworkInformation.Ping
$reply=$ping.send($CName)
if ($reply.status -eq "Success")
{
Write-Host "Computer: $CName "
"$CName"|Out-File $ComputerInfoFile -Encoding ASCII -Append
$data = @()
$NetLogs = Get-WmiObject Win32_NetworkLoginProfile -ComputerName $CName

foreach ($NetLog in $NetLogs) {
if ($NetLog.LastLogon -match "(\d{14})") {
$row = "" | Select Name,LogonTime
$row.Name = $NetLog.Name
$row.LogonTime=[datetime]], "yyyyMMddHHmmss", $null)
$data += $row
}
}
$data
$data | Out-File $ComputerInfoFile -encoding ASCII -append
}
else
{
Write-Host "Computer $CName Unreachable"
"$CName is unreachable" |Out-File $ComputerInfoFile -Encoding ASCII -Append
}
}

The output on the screen is:
Computer: Machine71

Name LogonTime
---- ---------
Machine71\Administrator 9/11/2008 8:00:53 AM
Domian1\FOSN00 9/6/2012 5:36:12 PM
Domain1\BRAC09 3/8/2012 9:45:38 AM
Domain1\SQLAdmin 9/6/2012 4:20:12 PM
Domain2\SQLAdmin 9/6/2012 4:20:12 PM
Domain2\MHPAppLaunch 9/7/2012 3:20:43 PM
Domain2\BRAC09 3/8/2012 9:45:38 AM
Exception calling "Send" with "1" argument(s): "An exception occurred during a
Ping request."
At C:\scripts\ComputerProfile.ps1:15 char:18
+ $reply=$ping.send <<<< ($CName)
+ CategoryInfo : NotSpecified: (:slight_smile: , MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

Computer: Machine58
Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x80070
6BA)
At C:\scripts\ComputerProfile.ps1:21 char:27
+ $NetLogs = Get-WmiObject <<<< Win32_NetworkLoginProfile -ComputerName $CNa
me
+ CategoryInfo : InvalidOperation: (:slight_smile: [Get-WmiObject], COMExcept
ion
+ FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands
.GetWmiObjectCommand

Computer: Machine59
Machine59\Administrator 7/7/2011 2:49:42 PM
Domain1\MCCP03 8/3/2012 3:11:38 PM
Domain1\KARJ01 9/6/2011 11:45:43 AM
Domain1\JENK00 4/25/2012 2:07:50 PM
Computer: Machine12
Machine12\Administrator 4/6/2011 10:29:09 AM
Domain1\DISJ10 9/7/2012 1:55:26 PM
Domain2\SQLAdmin 9/6/2012 4:20:12 PM
Domain2\BROD15 9/7/2012 10:54:01 AM

I would like to get just the past thirty days of the profiles lastlogon was in the past 30 days. Also, for machine not pingable (see Machine58), I would simply like to skip it without even generating errors (takes too much time while crawling the domain for thousands of machines).
by coderaven at 2012-09-07 14:32:21
Looks like you are getting some good results now.

When trying to get the last 30 days, you can do a simple compare like this:

if ($row.LogonTime -gt ([datetime]::AddDays(-30)) { $data+=$row}

Just make sure your LogonTime is a proper date at this point.
Solving the problem about computers being online or not is also built in, you don’t need to create the ping object.

if (Test-Connection -ComputerName <your computername here> -Count 1 -Quiet) { get your data... } else { write-host "<$Computer...> is offline." }
by poshoholic at 2012-09-07 14:42:52
One minor correction to the above: that should say
[datetime]]
or
(GetDate).AddDays(-30)
Both do the same thing.
by rambog at 2012-09-10 06:47:30
Thanks Kirk, I got it working.

Can anyone tell me what the match does in the following line, if ($NetLog.LastLogon -match "(\d{14})") ?

Some machines in my domain have corrupted WMI. Would rewriting the script to use ps-drive to comb through the registry hierarchy circumvent this problem (or does it also somehow rely on WMI)?
by willsteele at 2012-09-10 06:58:35
The match there compares values against a 14 character long (the 14 part) digit (the d part) regular expression. If the value passed via $NetLog.LastLogon is a 14-digit string, the match returns $true.

If you have truly corrupted WMI repositories you would more than likely need to rebuild them with WMI. Changing how you approach the data (registry versus WMI) may get you around the problem, but, it depends on where the data really lives.
by rambog at 2012-09-10 07:27:53
The output from my script shows profiles of varying lengths. A quick check showed some profiles having 11 characters (domain\userid) while another showed 18. I simply want to show all profiles that have been accessed/used over the past 30 days. Am I somehow missing something by does an exact match on a 14-digit string?
by willsteele at 2012-09-10 07:35:59
If I am reading this correctly, the part you mentioned it simply ensuring that there is a valid DateTime value for that on property before concatenating the profile name and the reformatted DateTime. By adding the -match test you ensure there is in fact a 14-digit string for that property. This is merely validation mechanism as best I can tell to eliminate entries which do not have values or for which the values are empty/null, etc. If the bolded line below fails, it simply does not return a record.

foreach ($NetLog in $NetLogs) {
[b]if ($NetLog.LastLogon -match "(\d{14})")[/b] {
$row = "" | Select Name,LogonTime
$row.Name = $NetLog.Name
$row.LogonTime=[datetime]::ParseExact($matches[0], "yyyyMMddHHmmss", $null)
$data += $row