Get Active Directory Attributes for Users on Legacy Exchange Servers - powershell

I would like to create a CSV file of the users on our Exchange 2003 servers, and include some attributes from their AD account. In particular, I would like to pull certain AD values for the users with RecipientTypeDetails = LegacyMailbox.
I have tried a few different methods for targeting and filtering (ldapfilter, filter, objectAttribute, etc.) these users, with little success. The Exchange 2003 PowerPack for PowerGUI was helpful, but permissions issues and using the Exchange_Mailbox class are not challenges I want to overcome.
I was finally able to create a working script, but it is very slow. The script I've created below is currently working, although it is on track to take about 4+ hours to complete. I'm am looking for suggestions for improving the efficiency of my script or otherwise obtaining this data in a quicker manner. Here is the script:
$ADproperties = 'City','Company','department','Description','DistinguishedName','DisplayName','FirstName','l','LastName','msExchHomeServerName','NTAccountName','ParentContainer','physicaldeliveryofficename','SamAccountName','useraccountcontrol','UserPrincipalName'
get-user -ResultSize Unlimited -ignoredefaultscope -RecipientTypeDetails LegacyMailbox | foreach {Get-QADUser $_.name -DontUseDefaultIncludedProperties -IncludedProperties $ADproperties} | select $ADproperties | epcsv C:\UserListBuilder\exchUsers.csv -notype
Any help you can provide will be greatly appreciated!

I solved this challenge using a script I already created which merges a CSV file with attributes from AD. Basically I used Get-Mailbox to generate a CSV list of all of the Exchange 2003 users, then use that list as input for Get-QADuser to pull the AD attributes I need, and couldn't pull with other cmdlets. The Merge-Object and Export-CSV functions were found from other users on the internet, both very handy functions. Below is a copy of the script:
Function Merge-Object($Base, $Additional)
{
ForEach ($Property in $($Additional | Get-Member -Type Property, NoteProperty))
{
$Base | Add-Member -MemberType NoteProperty -Name $Property.Name -Value $Additional.$($Property.Name) -ErrorAction SilentlyContinue
}
Return $Base
}
Function Export-CSV
{
[CmdletBinding(DefaultParameterSetName='Delimiter',
SupportsShouldProcess=$true, ConfirmImpact='Medium')]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[System.Management.Automation.PSObject]
${InputObject},
[Parameter(Mandatory=$true, Position=0)]
[Alias('PSPath')]
[System.String]
${Path},
#region -Append (added by Dmitry Sotnikov)
[Switch]
${Append},
#endregion
[Switch]
${Force},
[Switch]
${NoClobber},
[ValidateSet('Unicode','UTF7','UTF8','ASCII','UTF32','BigEndianUnicode','Default','OEM')]
[System.String]
${Encoding},
[Parameter(ParameterSetName='Delimiter', Position=1)]
[ValidateNotNull()]
[System.Char]
${Delimiter},
[Parameter(ParameterSetName='UseCulture')]
[Switch]
${UseCulture},
[Alias('NTI')]
[Switch]
${NoTypeInformation})
begin
{
# This variable will tell us whether we actually need to append
# to existing file
$AppendMode = $false
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Export-Csv',
[System.Management.Automation.CommandTypes]::Cmdlet)
#String variable to become the target command line
$scriptCmdPipeline = ''
# Add new parameter handling
#region Dmitry: Process and remove the Append parameter if it is present
if ($Append) {
$PSBoundParameters.Remove('Append') | Out-Null
if ($Path) {
if (Test-Path $Path) {
# Need to construct new command line
$AppendMode = $true
if ($Encoding.Length -eq 0) {
# ASCII is default encoding for Export-CSV
$Encoding = 'ASCII'
}
# For Append we use ConvertTo-CSV instead of Export
$scriptCmdPipeline += 'ConvertTo-Csv -NoTypeInformation '
# Inherit other CSV convertion parameters
if ( $UseCulture ) {
$scriptCmdPipeline += ' -UseCulture '
}
if ( $Delimiter ) {
$scriptCmdPipeline += " -Delimiter '$Delimiter' "
}
# Skip the first line (the one with the property names)
$scriptCmdPipeline += ' | Foreach-Object {$start=$true}'
$scriptCmdPipeline += '{if ($start) {$start=$false} else {$_}} '
# Add file output
$scriptCmdPipeline += " | Out-File -FilePath '$Path' -Encoding '$Encoding' -Append "
if ($Force) {
$scriptCmdPipeline += ' -Force'
}
if ($NoClobber) {
$scriptCmdPipeline += ' -NoClobber'
}
}
}
}
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
if ( $AppendMode ) {
# redefine command line
$scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
$scriptCmdPipeline
)
} else {
# execute Export-CSV as we got it because
# either -Append is missing or file does not exist
$scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
[string]$scriptCmd
)
}
# standard pipeline initialization
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Export-Csv
.ForwardHelpCategory Cmdlet
#>
}
###################################################################################################################################
# Script
###################################################################################################################################
# Get Start Time
$startDTM = (Get-Date)
$ADproperties = 'FirstName','LastName','userAccountControl','physicaldeliveryofficename','l','City','UserPrincipalName','NTAccountName','SamAccountName','ParentContainer','Description','msExchHomeServerName'
#$CSVdirectory = "C:\UserListBuilder\CSV\Exch2003\*.*" # Directory containing Exchange directory export CSV files, include *.*
$csv = "C:\UserListBuilder\CSV\Exch2003\ex03.csv"
$Outputfilename = "C:\UserListBuilder\Exchange2003-ADInfo.csv"
# Create a file of the legacy mailboxes
Get-Mailbox -ignoredefaultscope -ResultSize 'Unlimited' | where { $_.'RecipientTypeDetails' -eq [Microsoft.Exchange.Data.Directory.Recipient.RecipientTypeDetails]'LegacyMailbox' } | select 'DisplayName','SamAccountName','UserPrincipalName' | epcsv $csv -notype
$CurrentFile = Import-Csv $csv
foreach($Row in $CurrentFile)
{
$CurrentUser = $Row.'UserPrincipalName'
$CurrentUserADinfo = Get-QADUser -identity "$CurrentUser" -DontUseDefaultIncludedProperties -IncludedProperties $ADproperties | select-object $ADproperties
Merge-Object $Row $CurrentUserADinfo
$Row | Export-CSV -Path $Outputfilename -Append -NoTypeInformation
}
# Get End Time
$endDTM = (Get-Date)
# Echo Time elapsed
"Elapsed Time: $(($endDTM-$startDTM).totalseconds) seconds"

Related

Slow Processing Script in Powershell, Worklfow first steps

as i was wondering why my script takes so long i was seachring on google and also here in stackoverflow.
But all that i could find any close to helpful was this one here, Powershell Script Running Slowly
As I'm still pretty new to Powershell this is a little complicated to get through and take over to my script as i dont know how to handle those mentiond things anyway as i never heard of it before.
My Script is pretty easy and just gives me some Informations if there is something that returns an echo or not.
I wanted to "scan" our entire Network so I made an csv with out local Networks IP's and pass it to Powershell to "Ping" those.
But I realised that the "was not responing" part takes a long time to execute.
$list = Import-Csv -Path D:\ipcheck3.csv -UseCulture
$x=$list.IP
$ErrorActionPreference = "SilentlyContinue"
foreach ($y in $x)
{
try
{
if(Test-Connection $y -Count 1 -quiet)
{
write-host "$y responded"
$y | Export-Csv -Path D:\PingSucceded.csv -Append
}
else
{
Write-Host "$y was not responding"
$y | Export-Csv -Path D:\Pingfailed.csv -Append
}
}
catch
{
Write-Warning "Other Error occured"
}
}
There are not only Windows Clients out there so WMI is not an option and I don't know how to achvie this otherwise
EDIT:
After the Workflow input this is my "Try Version"
workflow Test-IPrange
{
Param
(
$IPs
)
$tocheck= $IPs.IP
foreach -parallel ($IP in $tocheck)
{
$pingsucceed = Test-Connection $IP -Count 1 -quiet
if($pingsucceed -eq "True")
{
$IP | Export-Csv -Path D:\testj.csv -Append
}
else
{
$IP | Export-Csv -Path D:\testn.csv -Append
}
}
}
Test-IPrange -IPs $(Import-Csv -Path D:\ipcheck3.csv -UseCulture)
My Output of Workflow Try
#TYPE System.String
PSComputerName,"PSShowComputerName","PSSourceJobInstanceId","Length"
localhost,"True","4e208e38-f7c2-492f-9d81-6583a103c3ac","12"
localhost,"True","4e208e38-f7c2-492f-9d81-6583a103c3ac","12"
With the Help of #Fourat
i edited my code to this form
Function Custom-Ping {
Param(
[string]$Address
)
$ping = ping $Address /w 1 /n 1
$result = ![string]::IsNullOrEmpty($ping -Like "*(0% Verlust)*")
return $result
}
$list = Import-Csv -Path D:\ipcheck3.csv -UseCulture
$x=$list.IP
$ErrorActionPreference = "SilentlyContinue"
foreach ($y in $x)
{
try
{
if(Custom-Ping $y)
{
Write-Host "$y responded"
$y | Export-Csv -Path D:\PingsuccededV3.csv -Append
}
else
{
Write-Host "$y was not responding"
$y | Export-Csv -Path D:\PingfailedV3.csv -Append
}
}
catch
{
Write-Warning "Textline from CMD Command or other Error"
}
}
which works properly good and is faster
I think that your process time is spoiled by the timeouts. If all your IPs are in the local network, try to reduce the timeout (because the default value is 5 seconds).
If you have Powershell 6 :
Test-Connection $y -Count 1 -quiet -TimeoutSeconds 1
If you don't, just use ping :
ping 58.47.45.1 /w 1 /n 1
You can also use a parallel for each loop, but it won't help much if you have multiple fails :
ForEach -Parallel ($x in $y)
{
...
}
UPDATE
In order to handle ping results, you can use a function like this (I used the keyword 'perte' because my computer is in French) :
Function Custom-Ping {
Param(
[string]$Address
)
$ping = ping $Address /w 1 /n 1
$result = ![string]::IsNullOrEmpty($ping -Like "*(perte 0%)*")
return $result
}
I've used Workflow to solve this issue my self. It's a few years ago I did it, so something better and newer is out there. But this works great for me...
I've ping over 2000 computers within a few Min...
workflow Test-ComputersConnection
{
Param
(
# Param1 help description
$Computernames#,
# Param2 help description
# [int]
# $Param2
)
foreach -parallel ($ComputerName in $Computernames)
{
$ConnectionTest = Test-Connection -ComputerName $ComputerName -ErrorAction SilentlyContinue -Count 1
if ($ConnectionTest.Address -eq $ComputerName) {
Write-Output $(Add-Member -MemberType NoteProperty -Name "Computername" -Value $ComputerName -InputObject $ConnectionTest -PassThru )
#Write-Verbose -Verbose -Message "[$($ComputerName)]: Replays on Ping."
}
Else {
#Write-Verbose -Verbose -Message "[$($ComputerName)]: Do not replays on Ping."
}
}
}
$OnlineNow0 = Test-ComputersConnection -Computernames $( Import-Csv -Path D:\ipcheck3.csv -UseCulture |
Select-Object -ExpandProperty name)
The code above is a quick edit of what I use... You will need to edit the $(Import ...) statement first, to make sure the PC name is being deliveret to the workflow.
I've just testet on my own computer and it gave me a reply...

Powershell Error handling not not working as expected with functions

Looking for advice on error handling in Powershell. I think I understand the concept behind using Try/Catch but I'm struggling on where to utilize this in my scripts or how granular I need to be.
For example, should I use the try/catch inside my functions and if so, should I insert the actions of my function inside the try or do I need to break it
down further? OR, should I try to handle the error when I call my function? Doing something like this:
Try{
Get-MyFunction
} catch{ Do Something"
}
Here's an example of a script I wrote which is checking for some indicators of compromise on a device. I have an application that will launch this script and capture the final output. The application requires the final output to be in the following format so any failure should generate this.
[output]
result=<0 or 1>
msg= <string>
Which I'm doing like this:
Write-Host "[output]"
Write-Host "result=0"
Write-Host "msg = $VariableContainingOutput -NoNewline
Two of my functions create custom objects and then combine these for the final output so I'd like to capture any errors in this same format. If one function generates an error, it should record these and continue.
If I just run the code by itself (not using function) this works but with the function my errors are not captured.
This needs to work on PowerShell 2 and up. The Add-RegMember and Get-RegValue functions called by this script are not shown.
function Get-ChangedRunKey {
[CmdletBinding()]
param()
process
{
$days = '-365'
$Run = #()
$AutoRunOutput = #()
$RunKeyValues = #("HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Microsoft\Windows\CurrentVersion\Run",
"HKU:\S-1-5-21-*\Software\Wow6432node\Microsoft\Windows\CurrentVersion\Run"
)
Try{
$Run += $RunKeyValues |
ForEach-Object {
Get-Item $_ -ErrorAction SilentlyContinue |
Add-RegKeyMember -ErrorAction SilentlyContinue |
Where-Object {
$_.lastwritetime -gt (Get-Date).AddDays($days)
} |
Select-Object Name,LastWriteTime,property
}
if ($Run -ne $Null)
{
$AutoRunPath = ( $Run |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$AutoRunValue = $AutoRunPath |
Where-Object {
$_ -and $_.Trim()
} |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
}
}
#Build Custom Object if modified Run keys are found
if($AutorunValue -ne $null)
{
foreach ($Value in $AutoRunValue) {
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = $Value.path
value = $Value.value
}
}
}
Write-Output $AutoRunOutput
}catch{
$AutoRunOutput += New-Object PSObject -Property #{
Description = "Autorun"
path = "N/A"
value = "Error accessing Autorun data. $($Error[0])"
}
}
}
}
function Get-ShellIOC {
[CmdletBinding()]
param()
process
{
$ShellIOCOutput = #()
$ShellIOCPath = 'HKU:\' + '*' + '_Classes\*\shell\open\command'
Try{
$ShellIOCValue = (Get-Item $ShellIOCPath -ErrorAction SilentlyContinue |
Select-Object name,property |
ForEach-Object {
$_.name
}
) -replace "HKEY_LOCAL_MACHINE", "HKLM:" -replace "HKEY_Users", "HKU:"
$ShellIOCDetected = $ShellIOCValue |
ForEach-Object {
Get-RegValue -path $_ -Name '*' -ErrorAction SilentlyContinue
} |
Where-Object {
$_.value -like "*cmd.exe*" -or
$_.value -like "*mshta.exe*"
}
if($ShellIOCDetected -ne $null)
{
foreach ($ShellIOC in $ShellIOCDetected) {
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = $ShellIOC.path
value = $ShellIOC.value
}
}
}
Write-Output $ShellIOCOutput
}catch{
$ShellIOCOutput += New-Object PSObject -Property #{
Description = "Shell_IOC_Detected"
path = "N/A"
value = "Error accessing ShellIOC data. $($Error[0])"
}
}
}
}
function Set-OutputFormat {
[CmdletBinding()]
param()
process
{
$FormattedOutput = $AutoRunOutput + $ShellIOCOutput |
ForEach-Object {
"Description:" + $_.description + ',' + "Path:" + $_.path + ',' + "Value:" + $_.value + "|"
}
Write-Output $FormattedOutput
}
}
if (!(Test-Path "HKU:\")){
try{
New-PSDrive -PSProvider Registry -Root HKEY_USERS -Name HKU -ErrorAction Stop | Out-Null
}catch{
Write-Output "[output]"
Write-Output "result=0"
Write-Host "msg = Unable to Connect HKU drive" -NoNewline
}
}
$AutoRunOutput = Get-ChangedRunKey
$ShellIOCOutput = Get-ShellIOC
$FormattedOutput = Set-OutputFormat
Write-Output "[output]"
if ($FormattedOutput -eq $Null)
{
Write-Output "result=0"
Write-Host "msg= No Items Detected" -NoNewline
}
else
{
Write-Output "result=1"
Write-Host "msg=Items Detected: $($FormattedOutput)" -NoNewline
}
You have to know that there are 2 error types in PowerShell:
Terminating Errors: Those get caught automatically in the catch block
Non-Terminating Error: If you want to catch them then the command in question needs to be execution using -ErrorAction Stop. If it is not a PowerShell command but an executable, then you need to check stuff like the exit code or $?. Therefore I suggest wrapping your entire action in an advanced function on which you then call using -ErrorAction Stop.
Apart from that I would like to remark that PowerShell version 2 has already been deprecated. The reason for why non-terminating errors exists is because there are cases like for example processing multiple objects from the pipeline where you might not want it to stop just because it did not work for one object. And please do not use Write-Host, use Write-Verbose or Write-Output depending on the use case.

Changing CSV report entry if duplicates exist with powershell

I'm pretty new to powershell, so I'll do my best to explain myself. I'm currently working with a script which creates a csv report of access levels for all users and user groups on a server. Breaking it down between Admin and User privileges. As is, it currently out puts two entries for groups which have both Admin and User access. Resembling the following image. (posted as an image due to some trouble with creating a table on stackoverflow)
I was hoping for some suggestions about how to consolidate users/groups which repeat in the report into one entry with an X in both fields. Kind of like the following:
Here is my current script:
[CmdletBinding()]
Param(
[Parameter( ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true
)]
[string[]]
$ComputerName = $env:ComputerName,
[Parameter()]
[array] $LocalGroupNames = #("Administrators", "Remote Desktop Users"),
[Parameter()]
[string]
$LocalGroupName,
[Parameter()]
[string]
$OutputDir = "c:\temp"
)
Begin {
$OutputFile = Join-Path $OutputDir "OSUsers $(get-date -f yyyy-MM-dd).csv"
Add-Content -Path $OutPutFile -Value "ServerName, User\Group, Administrator, User"
}
Process {
ForEach($Computer in $ComputerName) {
foreach ($LocalGroupName in $LocalGroupNames) {
Write-host "Working on $Computer"
If(!(Test-Connection -ComputerName $Computer -Count 1 -Quiet)) {
Add-Content -Path $OutputFile -Value "$Computer,Offline,Offline,Offline"
Continue
} else {
Write-Verbose "Working on $computer"
try {
$group = [ADSI]"WinNT://$Computer/$LocalGroupName"
$members = #($group.Invoke("Members"))
Write-Verbose "Successfully queries the members of $computer"
if(!$members) {
Add-Content -Path $OutputFile -Value "$Computer,NoMembersFound"
Write-Verbose "No members found in the group"
continue
}
}
catch {
Add-Content -Path $OutputFile -Value "$Computer,FailedToQuery"
Continue
}
foreach($member in $members) {
try {
$MemberName = $member.GetType().Invokemember("Name","GetProperty",$null,$member,$null)
$MemberType = $member.GetType().Invokemember("Class","GetProperty",$null,$member,$null)
$MemberPath = $member.GetType().Invokemember("ADSPath","GetProperty",$null,$member,$null)
$MemberDomain = $null
if($MemberPath -match "^Winnt\:\/\/(?<domainName>\S+)\/(?<CompName>\S+)\/") {
if($MemberType -eq "User") {
$MemberType = "LocalUser"
} elseif($MemberType -eq "Group"){
$MemberType = "LocalGroup"
}
$MemberDomain = $matches["CompName"]
} elseif($MemberPath -match "^WinNT\:\/\/(?<domainname>\S+)/") {
if($MemberType -eq "User") {
$MemberType = "DomainUser"
} elseif($MemberType -eq "Group"){
$MemberType = "DomainGroup"
}
$MemberDomain = $matches["domainname"]
} else {
$MemberType = "Unknown"
$MemberDomain = "Unknown"
}
if ($MemberType -ne "Unknown") ##Exclude unresolved users
{
if ($LocalGroupName -eq "Administrators")
{Add-Content -Path $OutPutFile -Value "$Computer, $MemberName, X, ,"}
if ($LocalGroupName -eq "Remote Desktop Users")
{Add-Content -Path $OutPutFile -Value "$Computer, $MemberName, , X,"}
}
} catch
{
Add-Content -Path $OutputFile -Value "$Computer, ,FailedQueryMember"
}
}
}
}
}
}
End {}
What you may want to do here is get the members of both the Administrators and Remote Desktop Users, and then with a foreach statement, check the results for each user's presence in both groups or not.
Compare-Object might be your friend here as well, as you can determine how to handle what you write to your file. That command will output "<=", "=>", or "==" if you use the -IncludeEqual parameter.
Ex:
Powershell - find items which are in array1 but NOT in array2

Exporting each variable value in loop

I am trying to capture the changing variable '$server' everytime the parameters go through a foreach loop. To summarize, the $sever value is always changing, and I want to capture it and add it into a collective csv file
Thank you!
Here is the code main part of the code that I have.
function Convert-QueryToObjects
{
[CmdletBinding()]
[Alias('QueryToObject')]
[OutputType([PSCustomObject])]
param
(
[Parameter(Mandatory = $false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[Alias('ComputerName', 'Computer')]
[string]
$Name = $env:COMPUTERNAME
)
Process
{
Write-Verbose "Running query.exe against $Name."
$Users = query user /server:$Name 2>&1
if ($Users -like "*No User exists*")
{
# Handle no user's found returned from query.
# Returned: 'No User exists for *'
Write-Error "There were no users found on $Name : $Users"
Write-Verbose "There were no users found on $Name."
}
elseif ($Users -like "*Error*")
{
# Handle errored returned by query.
# Returned: 'Error ...<message>...'
Write-Error "There was an error running query against $Name : $Users"
Write-Verbose "There was an error running query against $Name."
}
elseif ($Users -eq $null -and $ErrorActionPreference -eq 'SilentlyContinue')
{
# Handdle null output called by -ErrorAction.
Write-Verbose "Error action has supressed output from query.exe. Results were null."
}
else
{
Write-Verbose "Users found on $Name. Converting output from text."
# Conversion logic. Handles the fact that the sessionname column may be populated or not.
$Users = $Users | ForEach-Object {
(($_.trim() -replace ">" -replace "(?m)^([A-Za-z0-9]{3,})\s+(\d{1,2}\s+\w+)", '$1 none $2' -replace "\s{2,}", "," -replace "none", $null))
} | ConvertFrom-Csv
Write-Verbose "Generating output for $($Users.Count) users connected to $Name."
# Output objects.
foreach ($User in $Users)
{
Write-Verbose $User
if ($VerbosePreference -eq 'Continue')
{
# Add '| Out-Host' if -Verbose is tripped.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
} | Out-Host
}
else
{
# Standard output.
[PSCustomObject]#{
ComputerName = $Name
Username = $User.USERNAME
SessionState = $User.STATE.Replace("Disc", "Disconnected")
SessionType = $($User.SESSIONNAME -Replace '#', '' -Replace "[0-9]+", "")
}
}
}
}
}
}
$Servers = Get-Content 'H:\demo\computernames.txt'
foreach ($Server in $Servers)
{
if (-not( Test-Connection $Server -Count 1 -Quiet )) { continue }
if (-not( Convert-QueryToObjects $Server -ErrorAction SilentlyContinue)) {
$server | Out-File 'H:\demo\session\run1.csv' -Append
}
else
{
Convert-QueryToObjects -Name $Server | select ComputerName, Username, Sessionstate, IdleTime, ID | Export-Csv 'H:\demo\session\run.csv' -NoTypeInformation
}
}
Create an array outside of your foreach loop and add the $server variable value to the array during your foreach. At the end export the array to a csv.
Not tested, but are you wanting to do something like this?
Get-Content "H:\demo\computernames.txt" | ForEach-Object {
$computerName = $_
if ( Test-Connection $computerName -Count 1 -Quiet ) {
Convert-QueryToObjects $computerName -ErrorAction SilentlyContinue
}
else {
"$_ not pingable" | Out-File "H:\demo\session\notpingable.log" -Append
}
} | Export-Csv "H:\demo\session\run.csv" -NoTypeInformation

Alternative to Append export-csv powershell

I add user logon and logout tracking script
I found that some computers do not export csv as they have powershell 2.0 because append is not supported is there any alternative?
$ErrorActionPreference = 'Continue'
####**** Tracking user logon *****#####
$username = $env:USERNAME
$computername = $env:COMPUTERNAME
$ipv4 = Test-Connection -ComputerName (hostname) -Count 1 | foreach { $_.ipv4address }
$ipv6 = Test-Connection -ComputerName (hostname) -Count 1 | foreach { $_.ipv6address }
$timeformat='MM-dd-yyyy hh:mm:ss tt'
$time = (Get-Date).ToString($timeformat)
$action = 'Logon'
$filedate = 'MM-dd-yyyy'
$filename = 'CompInfo' + ' ' + $(Get-Date).ToString($filedate)
#Creates custom table and sorts the information
$table= New-Object –TypeName PSObject -Property #{
'Date/Time' = $time
'Username' = $username
'ComputerName'= $computername
'IPv4 Address' = $ipv4
'IPv6 Address' = $ipv6
'Notes/Action' = $action
} | Select date/time, username, computername, 'IPv4 Address', 'IPv6 Address', notes/action
$table | Export-Csv "d:\$env:username.csv" -NoClobber -append -NoTypeInformation
Try this
#Thanks to Dmitry Sotnikov
#https://dmitrysotnikov.wordpress.com/2010/01/19/export-csv-append/
#### Append CSV Powershell 2.0
function Export-CSV {
[CmdletBinding(DefaultParameterSetName='Delimiter',
SupportsShouldProcess=$true, ConfirmImpact='Medium')]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[System.Management.Automation.PSObject]
${InputObject},
[Parameter(Mandatory=$true, Position=0)]
[Alias('PSPath')]
[System.String]
${Path},
#region -Append (added by Dmitry Sotnikov)
[Switch]
${Append},
#endregion
[Switch]
${Force},
[Switch]
${NoClobber},
[ValidateSet('Unicode','UTF7','UTF8','ASCII','UTF32',
'BigEndianUnicode','Default','OEM')]
[System.String]
${Encoding},
[Parameter(ParameterSetName='Delimiter', Position=1)]
[ValidateNotNull()]
[System.Char]
${Delimiter},
[Parameter(ParameterSetName='UseCulture')]
[Switch]
${UseCulture},
[Alias('NTI')]
[Switch]
${NoTypeInformation})
begin
{
# This variable will tell us whether we actually need to append
# to existing file
$AppendMode = $false
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Export-Csv',
[System.Management.Automation.CommandTypes]::Cmdlet)
#String variable to become the target command line
$scriptCmdPipeline = ''
# Add new parameter handling
#region Dmitry: Process and remove the Append parameter if it is present
if ($Append) {
$PSBoundParameters.Remove('Append') | Out-Null
if ($Path) {
if (Test-Path $Path) {
# Need to construct new command line
$AppendMode = $true
if ($Encoding.Length -eq 0) {
# ASCII is default encoding for Export-CSV
$Encoding = 'ASCII'
}
# For Append we use ConvertTo-CSV instead of Export
$scriptCmdPipeline += 'ConvertTo-Csv -NoTypeInformation '
# Inherit other CSV convertion parameters
if ( $UseCulture ) {
$scriptCmdPipeline += ' -UseCulture '
}
if ( $Delimiter ) {
$scriptCmdPipeline += " -Delimiter '$Delimiter' "
}
# Skip the first line (the one with the property names)
$scriptCmdPipeline += ' | Foreach-Object {$start=$true}'
$scriptCmdPipeline += '{if ($start) {$start=$false} else {$_}} '
# Add file output
$scriptCmdPipeline += " | Out-File -FilePath '$Path'"
$scriptCmdPipeline += " -Encoding '$Encoding' -Append "
if ($Force) {
$scriptCmdPipeline += ' -Force'
}
if ($NoClobber) {
$scriptCmdPipeline += ' -NoClobber'
}
}
}
}
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
if ( $AppendMode ) {
# redefine command line
$scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
$scriptCmdPipeline
)
} else {
# execute Export-CSV as we got it because
# either -Append is missing or file does not exist
$scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
[string]$scriptCmd
)
}
# standard pipeline initialization
$steppablePipeline = $scriptCmd.GetSteppablePipeline(
$myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
}
#### Append CSV Powershell 2.0
$ErrorActionPreference = 'Continue'
####**** Tracking user logon *****#####
$username = $env:USERNAME
$computername = $env:COMPUTERNAME
$ipv4 = Test-Connection -ComputerName (hostname) -Count 1 | foreach { $_.ipv4address }
$ipv6 = Test-Connection -ComputerName (hostname) -Count 1 | foreach { $_.ipv6address }
$timeformat='MM-dd-yyyy hh:mm:ss tt'
$time = (Get-Date).ToString($timeformat)
$action = 'Logon'
$filedate = 'MM-dd-yyyy'
$filename = 'CompInfo' + ' ' + $(Get-Date).ToString($filedate)
#Creates custom table and sorts the information
$table= New-Object –TypeName PSObject -Property #{
'Date/Time' = $time
'Username' = $username
'ComputerName'= $computername
'IPv4 Address' = $ipv4
'IPv6 Address' = $ipv6
'Notes/Action' = $action
} | Select date/time, username, computername, 'IPv4 Address', 'IPv6 Address', notes/action
$table | Export-Csv "D:\$env:username.csv" -NoClobber -Append -Delimiter ',' -NoTypeInformation