Powershell - Start-ThreadJob Ids Incrementing Very Quickly, Why? - arrays

New to Powershell and Stackoverflow. Here's my first Powershell Script that I'm trying to optimize to the best of my abilities. My goal is to have the code run as efficiently as possible. Any help/suggestions on that front would be much appreciated!
This script shows new 'Established' TCP Connections (Get-NetTCPConnection) and their associated DNS Hostnames (Resolve-DnsName). Each new Connection is compared to an array of previous Connections. If they have the same 'RemoteAddress', the DNS Hostname is copied over to the new Connection and displayed; otherwise it creates a new (Resolve-DnsName) (Start-ThreadedJob), and moves on to the next new Connection. Once a Job is 'Completed' it copies over the 'NameHost' and displays the Connection.
I have hit a roadblock in my understanding. When the code is running, the Job 'Ids' seem to be incrementing very quickly even though no new Jobs where created in between the last Job and the new Job.
To test the script, run it and visit any Site. Watch as the 'Id' increment very quickly. Please note that it will create a Log File in "C:\Temp\Active_Connections.csv"
$logFile = 'C:\Temp\Active_Connections.csv'
if (-not(Test-Path $logFile -PathType Leaf)){
New-Item -ItemType File -Force -Path $logFile | Out-Null
} else {
Clear-Content $logFile
}
$headersAdded = $true
$newConnections = #()
While ($true){
$connections = #(Get-NetTCPConnection)
foreach ($connection in $connections){
if ($connection.State -eq "Established"){
if ($newConnections.InstanceID -notcontains $connection.InstanceID){
if ($newConnections.RemoteAddress -notcontains $connection.RemoteAddress){
if ((Get-Job).Name -notcontains $connection.RemoteAddress){
Start-ThreadJob -Name $connection.RemoteAddress -ScriptBlock {param($remoteAddress) Resolve-DNSName -Name $remoteAddress} -ArgumentList $connection.RemoteAddress >$null
}else{
$job = Get-Job | Where-Object {$_.Name -eq $connection.RemoteAddress}
if ($job.State -eq "Completed"){
Add-Member -InputObject $connection -MemberType NoteProperty -Name "Id" -Value $job.Id -Force
Try {
$receivedJob = $job | Receive-Job -ErrorAction Stop
Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $receivedJob.NameHost -Force
}catch{
$na = "N/A"
Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $na -Force
}
#Remove-Job -Id $job.Id
}
}
}else{
foreach ($newConnection in $newConnections){
if ($newConnection.RemoteAddress -eq $connection.RemoteAddress){
Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $newConnection.NameHost -Force
}
}
}
}
if ($null -ne $connection.NameHost){
if ($headersAdded) {
$formatting = #{n='CreationTime';e={$_.CreationTime.ToString("h:mm:ss tt")}},'Id','LocalAddress','LocalPort','RemoteAddress','RemotePort','NameHost'
$properties = #{Expression="CreationTime";Width=13},#{Expression="Id";Width=4},#{Expression="LocalAddress";Width=15},#{Expression="LocalPort";Width=10;Alignment="Left"},#{Expression="RemoteAddress";Width=15},#{Expression="RemotePort";Width=10;Alignment="Left"},#{Expression="NameHost";Width=100}
($connection | Select-Object $formatting | Format-Table -Property $properties | Out-String).Trim() | Tee-Object -FilePath $logFile -Append
$headersAdded = $false
} else {
($connection | Select-Object $formatting | Format-Table -HideTableHeaders -Property $properties | Out-String).Trim() | Tee-Object -FilePath $logFile -Append
}
$newConnections += $connection
}
}
}
}
Please, let me know what I can do better and if you have any ideas as to why the Job Id's are incrementing so quickly between new Connections.
Appreciate the help,
Chris

I have no explanation for the jumps in job ID values. While it would be good to know the reason, pragmatically speaking, it isn't necessarily a problem.
Your code creates a tight loop which is best avoided.
The following is a PowerShell-idiomatic reformulation of your code that tries to get results as early as possible, while sleeping a fixed amount of time between tries (which you can obviously adjust).
The upshot is that the output objects won't necessarily be ordered chronologically.
The Id property (column) of the output objects reflects the original output order as returned by Get-NetTCPConnection
# NOTE: What is created is NOT a CSV file.
# It is a plain-text file in tabular format FOR THE HUMAN OBSERVER.
$logFile = 'C:\Temp\Active_Connections.csv'
& {
$newConnections = [ordered] #{} # (Ordered) hashtable that stores all new connections.
while ($true) {
# Look for new connections, and start a thread job for each
# in order to resolve the remote adddress to a domain name, if possible.
Get-NetTCPConnection |
Where-Object { $_.State -eq 'Established' -and -not $newConnections.Contains($_.InstanceID) } |
ForEach-Object {
$jb = Start-ThreadJob { Resolve-DNSName -Name ($using:_).RemoteAddress }
$newConnections[$_.InstanceID] =
$_ |
Select-Object CreationTime,
#{
n = 'Id'
e = { $jb.Id }
},
LocalAddress, LocalPort, RemoteAddress, RemotePort,
#{
n = 'NameHost'
e = { $jb }
}
}
# Sleep a little, to avoid a tight loop.
Start-Sleep -Milliseconds 300
# Look for thread jobs that have completed, and output
# the connection-info objects with the job result.
$newConnections.Keys |
ForEach-Object {
if (($obj = $newConnections[$_]) -and ($jb = $obj.NameHost).State -notin 'NotStarted', 'Running') {
# A completed job: get its result.
$result = try { $jb | Receive-Job -ErrorAction Stop } catch { #{ NameHost = 'n/a' } }
$jb | Remove-Job -Force # Remove the completed job.
$obj.NameHost = $result.NameHost # Update the object with the job result.
$obj # Output the updated object.
$newConnections[$_] = $null # No need to hang on to the object in the hasthable.
}
}
}
} |
Format-Table #{ Name = 'CreationTime'; Expression = { $_.CreationTime.ToString('h:mm:ss tt') }; Width = 13 },
#{Expression = "Id"; Width = 4 },
#{Expression = "LocalAddress"; Width = 15 },
#{Expression = "LocalPort"; Width = 10; Alignment = "Left" },
#{Expression = "RemoteAddress"; Width = 15 }, #{Expression = "RemotePort"; Width = 10; Alignment = "Left" },
#{Expression = "NameHost"; Width = 100 } |
Tee-Object -FilePath $logFile

Related

PowerShell Get Windows OS Version Fast and Do Different Things

Is there a faster way to get a specific registry value from a list of servers? I'm selecting a text file of computers with different flavors of windows and getting the OS product name. I'm finding that it's taking a couple seconds per computer to retrieve.
Current script:
Clear-Host
# Prompt for file containing list of target
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$myDialog = New-Object System.Windows.Forms.OpenFileDialog
$myDialog.Title = "Select File of Target Systems"
$myDialog.InitialDirectory = $PSScriptRoot
$myDialog.Filter = "TXT (*.txt) | *.txt"
$result = $myDialog.ShowDialog()
If ($result -eq "OK") {
$Computers = Get-Content $myDialog.FileName
}
Else {
Write-Host "`nCancelled by User`n"
}
$Array = #()
# Loop Through Computers
ForEach ($Computer in $Computers) {
Write-Warning "Processing $Computer"
# Get Registry Values
Try {
$OSVersion = Invoke-Command -ComputerName $Computer -ScriptBlock { (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName }
# Create a custom object
$ComplexObject = New-Object PSCustomObject
$ComplexObject | Add-Member -MemberType NoteProperty -Name "Server name" -Value $Computer
$ComplexObject | Add-Member -MemberType NoteProperty -Name "OS Version" -Value $OSVersion
# Add custom object to our array
$Array += $ComplexObject
}
Catch {
$_.Exception.Message
Break
}
}
# Results
If ($Array) {
# Display results in new window
$Array | Out-GridView -Title "OS Version Results"
# Display results in PS console
$Array
}
My end goal later on in the script is to do different things based on the OS version so I want to separate them into independent lists:
If (We have Win2008 servers) {
"Do This"
}
If (We have Win2012R2 servers) {
"Do This"
}
Clear-Host
# Prompt for file containing list of target
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$myDialog = [System.Windows.Forms.OpenFileDialog]::new()
$myDialog.Title = "Select File of Target Systems"
$myDialog.InitialDirectory = $PSScriptRoot
$myDialog.Filter = "TXT (*.txt) | *.txt"
$result = $myDialog.ShowDialog()
If ($result -eq "OK") {
$Computers = Get-Content $myDialog.FileName
}
Else {
Write-Host "`nCancelled by User`n"
}
# Get Registry Values
$Array = Try {
Invoke-Command -ComputerName $Computers -ScriptBlock {
(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName).ProductName
} -ErrorAction stop | Select-Object #{n="Server Name";e={$_.pscomputername}},
#{n="OS Version";e={$_}}
}
Catch {
write-warning $_.Exception.Message
break
}
# Results
If ($Array) {
# Display results in new window
$Array | Out-GridView -Title "OS Version Results"
# Display results in PS console
$Array
}
You can use Get-AdComputer like:
Get-ADComputer -Filter {(OperatingSystem -like "*windows*server*") -and (Enabled -eq "True")} -Properties OperatingSystem | Select -ExpandProperty OperatingSystem | ForEach {
If($_ -match "Windows Server 2008.*"){
# Server 2008
}
If($_ -match "Windows Server 2012.*"){
# Server 2012
}
# Add more like 2016,2019
}

Fill object within an loop and output the whole array to a CSV

I currently have a script that will run through a folder and open each PDF inside and an input box will pop up asking me what I want to rename the file to. This part works fine but I'm trying to populate an array with information on each file such as date created, date modified, old file name and new file name.
The code I currently have is:
Add-Type -AssemblyName Microsoft.VisualBasic
$folderpath = 'file path'
$items = Get-ChildItem -Recurse $folderpath *.pdf
$newFileName = ""
$counterID = 0
$amountOfScans = $items.Length
$typeOfScanner = ""
$scanArray = #()
$scanObject = New-Object System.Object
foreach( $i in $items) {
Start-Process ((Resolve-Path ("$folderpath$i")).Path)
Start-Sleep -Seconds 1
$counterID++
$orderID = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the new file name for scan $counterID / $amountOfScans :", $i)
if ($newFileName -eq "delete"){
$reasonForDeletion = [Microsoft.VisualBasic.Interaction]::InputBox("Enter a reason for marking $i for deletion :", $i)
}
if ($i.Length -eq 24){
$typeOfScanner = "Xerox"
}
elseif ($ie.Length -eq 33) {
$typeOfScanner = "Lexmark"
}
$DateCreated = Get-ItemProperty ((Resolve-Path ("$folderpath$i")).Path) | select -exp CreationTime
$DateModified1 = Get-Date -Format dd/MM/yyyy
$DateModified2 = Get-Date -Format HH:mm:ss
$DateModifiedTotal = ("$DateModified1 $DateModified2")
$scanObject[$counterID] | Add-Member -type NoteProperty -name TempName -Value "$i"
$scanObject[$counterID] | Add-Member -type NoteProperty -name NewName -Value "$orderID"
$scanObject[$counterID] | Add-Member -type NoteProperty -name TypeOfScanner -Value "$typeOfScanner"
$scanObject[$counterID] | Add-Member -type NoteProperty -name DateCreated -Value "$DateCreated"
$scanObject[$counterID] | Add-Member -type NoteProperty -name DateModified -Value "$DateModifiedTotal"
$scanObject[$counterID] | Add-Member -type NoteProperty -name ReasonForDeletion -Value "$reasonForDeletion"
$scanArray += $scanObject[$counterID]
Stop-Process -Name "Acro*"
}
$scanArray | export-csv C:\Scans\renamed_stats.csv -notypeinformation
However after this script is ran "renamed_stats.csv" is completely blank with no error messages.
Any help would be appreciated :)
Assuming you don't have so many objects as to run out of memory, and that your value variables like $i contain the expected, you can get rid of $scanObject altogether and do this:
[array]$scanArray += [PSCustomObject] #{
TempName = "$i"
NewName = "$orderID"
TypeOfScanner = "$typeOfScanner"
DateCreated = "$DateCreated"
DateModified = "$DateModifiedTotal"
ReasonForDeletion = "$reasonForDeletion"
}
You could also improve the date part by using quotes:
$DateModified = Get-Date -Format "dd/MM/yyyy HH:mm:ss"
Try creating $scanObject inside loop this way:
$scanObject = New-Object PSObject -Property #{
TempName = "$i"
NewName = "$orderID"
TypeOfScanner = "$typeOfScanner"
DateCreated = "$DateCreated"
DateModified = "$DateModifiedTotal"
ReasonForDeletion = "$reasonForDeletion"
}

Powershell - foreach to array as job (local / multi-threaded)

I'm trying to parse a site to collect price and product details. The script works in a loop however it's very slow. So I'm trying to run a multi-threaded powershell script as a job.
I've tried a lot of suggestions but I'm struggling to get the results out even though I can see its working (the web-request screen flashing up)
I'm only selecting the last 10 but I'll put in a throttle later. Just can't get it to output. Essentially I'd like all results to flow back into $arr.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product,$arr)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
$arr += $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product,$arr
}
$data = Get-Job * | Receive-Job
#Show Array
$arr
So you would want to use runspaces for that. Runspaces is a pretty complicated thing, luckily we have Posh-RSJob which handles everything for you. https://github.com/proxb/PoshRSJob
You can pass in the script block, so you would need very little adjustments.
Probably something like this:
foreach ($product in $ImportedProducts){
Start-RSJob -ScriptBlock $ScriptBlock
}
Get-RSjob | Receive-RSJob
If you want to get the results into $arr, you can't do it from within the script block as you are attempting to do. Multiple script blocks running in parallel cannot be allowed to access a single copy of a variable without taking additional steps not worth getting into.
The answer to your problem is going to be to write the output of each script block as regular output. That output is buffered until you use Receive-Job to get the results out of the job at which time you capture it into the $arr variable in a single threaded manner. Below is cod which should get you most of the way there.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
Write-Output $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product
}
do {
$arr += Get-Job -State Completed | Receive-Job -AutoRemoveJob
} while (Get-Job -State Running)
#Show Array
$arr

How do I convert an array object to a string object in PowerShell?

While trying to create an CSV file with information about certificates I have an issue to store the userrights on the private key.
The problem is that I want to store multiple values in one attribute so I use an array.
At first I had no errors, however the column in my csv-file remained empty even in the case where the array has a value.
With a simple Write-Host I can see my array has the expected value so this part works okay.
For further investigations I have added the line:
Get-Member $certs.GetValue("UserRights")
This gives an error indicating I have to convert my variable to a string-variable.
So next I have tried to convert this array to a single string.
I have tried several ways but my error doesn't disappear so it doesn't work.
Underneath is my full code with some former attempts commented.
cls $certs = Get-ChildItem cert:\LocalMachine -Recurse | Where-Object {-not $_.PSIsContainer} | Select * Write-Host ("There were {0} certificates" -f ($certs | Measure-Object).Count)
foreach($certificate in $certs) {
if($certificate.HasPrivateKey)
{
Write-Host "Certificate's PSChildName is" $certificate.PSChildName
$rsaFile = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$fullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" + $rsaFile
$acl = Get-Acl -Path $fullPath
foreach($accessrule in $acl.Access)
{
Write-Host "User" $accessrule.IdentityReference "has the following rights:" $accessrule.FileSystemRights
}
Write-Host "------"
$UserRechten = #()
foreach($accessrule in $acl.Access)
{
$UserRechten += "{0}:{1};" -f ($accessrule.IdentityReference,$accessrule.FileSystemRights)
}
Write-Host "================================================================"
# -join $UserRechten
# $Userrechten | out-string
# $UserRechten = [system.String]::Join(" ", $UserRechten)
$separator = ";"
[string]::Join($separator,$UserRechten)
$certs | Add-Member -MemberType NoteProperty -Name "UserRights" -Value $UserRechten -Force
Write-Host "UserRechten has value : "$UserRechten
Get-Member $certs.GetValue("UserRights")
Write-Host "================================================================"
} }
$Certs | Add-Member -MemberType NoteProperty -Name "MachineName" -Value $env:COMPUTERNAME -Force
# $certs | Add-Member -MemberType NoteProperty -Name "Store" -Value 'My' -Force $RunDate = Get-Date -Format 'yyyy-MM-dd' $certs | Add-Member -MemberType NoteProperty -Name "RunDate" -Value $RunDate -Force $certs | Add-Member -MemberType NoteProperty -Name "Owner" -Value $env:USERNAME -Force
$Certs | Select * | Export-Csv c:\Certificaten\LocalCertsAll_$env:COMPUTERNAME.csv
$Certs | Select MachineName, Owner, PSParentPath, DnsNameList, PSChildName, NotBefore, NotAfter, Rundate, EnhancedKeyUsageList, HasPrivateKey, SerialNumber, Issuer, Subject, FriendlyName, UserRigthts |
Export-CSV c:\Certificaten\Localcerts_$env:COMPUTERNAME.csv
As noted in the comments, Get-Member is probably not what you're looking for
You (almost certainly) don't want to add the UserRights member property to the $Certs array, but rather to the individual objects in $Certs.
(I removed a bunch of superfluous Write-Host statements for readability):
$CertsAmended = foreach($Certificate in $certs)
{
if($certificate.HasPrivateKey)
{
$rsaFile = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$fullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" + $rsaFile
$acl = Get-Acl -Path $fullPath
# Create the UserRights value using -join
$UserRechten = #(foreach($accessrule in $acl.Access){
Write-Host "User" $accessrule.IdentityReference "has the following rights:" $accessrule.FileSystemRights
"{0}:{1}" -f ($accessrule.IdentityReference,$accessrule.FileSystemRights)
}) -join ";"
# Add the property to the individual object
$Certificate | Add-Member -MemberType NoteProperty -Name "UserRights" -Value $UserRechten
Write-Host "Userrights: " $UserRechten
# "Drop" the certificate object (now with a UserRights value) back onto the pipeline
$Certificate
}
}
Now you can export the $CertsAmended array to CSV all you want
If you find the $var = #(foreach($item in $collection){}) -join ';' displeasing to the eye, break it into two statements:
$UserRechten = foreach($accessrule in $acl.Access)
{
# Create UserRight string here, without ;
}
$UserRechten = $UserRechten -join ';'
For the $fullPath variable, you may want to use the Join-Path cmdlet:
$fullPath = Join-Path "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" $rsaFile

Create Powershell Object From Loop Containing Multiple Users

I need to create a Powershell object, array or hash table to store a list of users and assorted details, these are extracted from a CSV file and located using Get-ADUser. As below:
$userList = Import-CSV $CSVInputFile
$users = #{}
Foreach ($csvUser in $userList)
{
$userSearchString = $csvUser | Select -ExpandProperty SamAccountName
$currentUser = (Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"})
If ($currentUser.EmailAddress -ne $null)
{
$currentUserEmailString = $csvUser | Select -ExpandProperty EmailAddress
$currentUserEmailString = ($currentUserEmailString -as [string])
$currentUser.EmailAddress = $currentUserEmailString
}
$Users = New-Object PSObject -Property #{
DistinguishedName = $currentUser.DistinguishedName
EmailAddress = $currentUser.EmailAddress
Enabled = $currentUser.Enabled
GivenName = $currentUser.GivenName
Name = $currentUser.Name
PasswordExpired = $currentUser.PasswordExpired
PasswordLastSet = $currentUser.PasswordLastSet
SamAccountName = $currentUser.SamAccountName
Surname = $currentUser.Surname
}
$Users
}
How can I add the details of each user for each iteration of the loop to the object.
I want to end up with an object containing the details of a number of users, same as the output directly from Get-ADUser:
Name SamAccountName EmailAddress
---- -------------- ------------
User1 user1 user1#domain.com
User2 user2 user2#domain.com
User3 user3 user3#domain.com
Any help would be greatly appreciated!
Not sure if I'm missing the point on this but I see you are building a custom object right in your loop. The only issue I do see is you are not keeping the results after each loop. Rather you are destroying the objects history.
I would change the declaration of $users to an array $users = #() and instead of populating a user hashtable into users add the current object into the array. You will then have an array of hashtables:
$Users += New-Object PSObject -Property #{...
Then you could the $Users output line outside the loop and you will have the whole thing. Then you could just output to a Select to get the output you desire.
$Users | Select-Object name,SamAccountName,EmailAddress
There is a potential major drawback of this approach though. When using += on arrays a new array is created and resized for the new element and the old array is discarded. This has huge performance implications for larger arrays.
An even better way to approach this would be to leverage the pipeline. This would be a performance boost when you have larger user groups.
Import-CSV $CSVInputFile | ForEach-Object{
$userSearchString = $_.SamAccountName
$currentUser = Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"}
If ($currentUser.EmailAddress -ne $null){
$currentUser.EmailAddress = $_.EmailAddress
}
[pscustomobject][ordered]#{
DistinguishedName = $currentUser.DistinguishedName
# ..... truncated
Surname = $currentUser.Surname
}
}
Now you could send that to something like Export-CSV or just save it into a variable. Your options are open now. [pscustomobject][ordered] are type accelerators available in PowerShell v3.0+
Define an $users as Array
$users = #()
and append the New-Object into $Users.
$Users += New-Object
Can't believe both of you guys got in before me! Oh well.
Hope this helps anyway.
$userList = Import-CSV $CSVInputFile
$users = #()
Foreach ($csvUser in $userList)
{
$userSearchString = $csvUser | Select -ExpandProperty SamAccountName
$currentUser = (Get-ADUser -Filter {SamAccountName -eq $userSearchString} `
-Properties PasswordExpired,PasswordLastSet,EmailAddress |
Where {$_.Enabled -eq "True"})
If ($currentUser.EmailAddress -ne $null)
{
$currentUserEmailString = $csvUser | Select -ExpandProperty EmailAddress
$currentUserEmailString = ($currentUserEmailString -as [string])
$currentUser.EmailAddress = $currentUserEmailString
}
#clears the properties of the previous object and starts collecting properties
$UserObj = New-Object PSObject
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "DistinguishedName" -Value $($currentUser.DistinguishedName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "EmailAddress" -Value $($currentUser.EmailAddress)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "Enabled" -Value $($currentUser.Enabled)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "GivenName" -Value $($currentUser.GivenName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "UserName" -Value $($currentUser.Name)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "PasswordExpired" -Value $($currentUser.PasswordExpired)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "PasswordLastSet" -Value $($currentUser.PasswordLastSet)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "SamAccountName" -Value $($currentUser.SamAccountName)
Add-Member -InputObject $UserObj -MemberType NoteProperty -Name "Surname" -Value $($currentUser.Surname)
#saves the properties in an array that exists outside of the loop to preserve information beyond one interation
$users += $UserObj
}
$users | Format-Table -Property UserName,SamAccountName,EmailAddress

Resources