I have a csv file
SERVERS|SEQUENCE|INDEX
ServerA| 1 | 1.1
ServerB| 1 | 2.1
ServerC| 2 | 3.1
And here is my Code
#importing csv into ps$
$csv = Import-Csv "sequencing.csv"
#Grouping & Sort data from csv
$objdata = $csv | Select SERVERS,SEQUENCE | Group SEQUENCE | Select #{n="SEQUENCE";e={$_.Name}},#{n="SERVERS";e={$_.Group | ForEach{$_.SERVERS}}} | Sort SEQUENCE
foreach ($d in $objdata)
{
$order = $d.SEQUENCE
$cNames = $d.SERVERS
if (Restart-Computer -ComputerName $cNames -Force -Wait -For Wmi -Delay 1 -ErrorAction SilentlyContinue)
{
write-host "$cNames is rebooting" -ForegroundColor Green
}
else
{
Write-Host "Unable to reboot $cNames remotely, Please do it manually" -ForegroundColor Red
}
}
I am trying to reboot multiple servers in sequence and piping the output through 2 different results.
From my code, all the servers will reboot but will output through the else statement.
Can anyone point me to the right direction?
Any help would be appreciated.
As Lee_Daily already pointed out, the Restart-Computer does not return anything. That means you will have to use a try/catch block.
To make sure that in the event of an error you actually enter the catch block, you need to set the ErrorAction parameter to Stop.
In your code, you show a csv to import that has the piping symbol | as delimiter, so you need to specify that on the Import-Csv cmdlet.
From your latest comment, I gather that you want to loop one computer at a time, instead of restarting multiple computers at once, so you will know which computer errors out.
Try:
# importing csv into ps$
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|'
# Grouping & Sort data from csv
$objdata = $csv | Select-Object SERVERS,SEQUENCE |
Group-Object SEQUENCE |
Select-Object #{Name = "SEQUENCE"; Expression = {$_.Name}},
#{Name = "SERVERS"; Expression = {$_.Group.SERVERS}} |
Sort-Object SEQUENCE
$objdata | ForEach-Object {
foreach ($server in $_.SERVERS) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}
}
Seeing your latest comment, I understand that you only want to reboot servers where the SEQUENCE value in the CSV is set to '1'. Correct?
Also, you want to restart the servers at the same time, but also want to be able to see what server did not restart. Maybe you can do that with running Restart-Computer -AsJob, but I believe that only works in PowerShell 5.1
Below code filters out all servers with SEQUENCE set to 1 and restarts them one at a time (as requested in your original question "I am trying to reboot multiple servers in sequence"):
# import csv data
$csv = Import-Csv "D:\sequencing.csv" -Delimiter '|' # change this to the delimiter actually used in the CSV !
# get the list of servers where SEQUENCE is set to '1'
$servers = $csv | Select-Object SERVERS,SEQUENCE |
Where-Object { $_.SEQUENCE.Trim() -eq '1' } |
ForEach-Object { $_.SERVERS.Trim() }
foreach ($server in $servers) {
try {
Restart-Computer -ComputerName $server -Force -Wait -For Wmi -Delay 1 -ErrorAction Stop
Write-Host "Server $server is rebooting" -ForegroundColor Green
}
catch {
Write-Host "Unable to reboot $server remotely, Please do it manually" -ForegroundColor Red
}
}
Related
get-content 'C:\assets.txt' | % {
$computer = $_
. 'c:\PSTools\PsLoggedon.exe' -accepteula -l -x \\$Computer 3>$null |
? {$_ -match '^\s{2,}((?<domain>\w+)\\(?<user>\S+))'} |
Select-Object `
#{n='Computer';e={$Computer}},
#{n='Domain';e={$matches.Domain}},
#{n='User';e={$Matches.User}} |
? user -notmatch '^Connecting$|^Users$|^NT$'
}
This is what I am using to get all of the currently logged on computers. Is there a way I can combine this with Get-ADUser so I ca pull straight from AD rather than from a txt document?
• Sorry, but currently there is no way through which you can integrate this ‘Psloggedon.exe’ utility with Active directory commands, i.e., ‘Get-AdUser’. But you can retrieve the details of currently logged on users on different computers in the network remotely by executing the below powershell function: -
‘ function Get-LoggedOnUser
{
[CmdletBinding()]
param
(
[Parameter()]
[ValidateScript({ Test-Connection -ComputerName $_ -Quiet -Count 1 })]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName = $env:COMPUTERNAME
)
foreach ($comp in $ComputerName)
{
$output = #{ 'ComputerName' = $comp }
$output.UserName = (Get-WmiObject -Class win32_computersystem -ComputerName $comp).UserName
[PSCustomObject]$output
}
} ‘
The above script will give you currently logged on users on several computer systems in the network that you pass on in place of ‘COMPUTERNAME’ as below. Please note that you must give a list of computers separated by commas when using the above script for multiple computer systems.
If you have AD in your environment, then you can check the Domain Controller logs to see when an Active Directory user account logs on and it will also tell the machine that the user is logged onto. Refer the below links for more on this: -
http://technet.microsoft.com/en-us/library/cc787176(v=ws.10).aspx
http://technet.microsoft.com/en-us/library/bb742435.aspx
http://www.windowsecurity.com/articles/deciphering-authentication-events-domain-controllers.html
Also, find the below link for more information and reference on the above: -
https://4sysops.com/archives/how-to-find-a-logged-in-user-remotely-using-powershell/
Powershell script to see currently logged in users (domain and machine) + status (active, idle, away)
You would use PowerShell's Get-ADComputer to do this job, not Get-ADUser. Here's a script which does all this work for you. The below mainly lifted from the public domain here and only slightly modified. It pulls and pipes all AD domain computers into C:\Computers.txt, then PS-remotes into each computer in that list to find the logged in, interactive user, and their last login date. Gives you a report file named C:\LoggedOnResults.txt in a nice tabled format.
# Finds and pipes all AD domain computers into Computers.txt, then PS-remotes into each computer in the list to find the logged in, interactive user, and their last login date. Generates a report file named C:\LoggedOnResults.txt, in a nice tabled format.
# Deletes the current file C:\Computers.txt (if it exists)
$FileName = "C:\Computers.txt"
if (Test-Path $FileName) {
Remove-Item $FileName
write-host "$FileName has been deleted"
}
else {
Write-host "$FileName doesn't exist"
}
# 0. Capture all AD computers into a text file named Computers.txt
# importing dependancy, assuming it's already installed.
# Install RSAT for Windows workstation, AD DS role for Windows Server if missing
Import-Module "ActiveDirectory"
Get-ADComputer -Filter {(OperatingSystem -like "*windows*") -and (Enabled -eq "True")} | Select -Expand Name | Out-File "C:\Computers.txt"
# 1. Create scriptblock to target computer will execute
$SB = {
$explorerprocesses = #(Get-WmiObject -Query "Select * FROM Win32_Process WHERE Name='explorer.exe'" -ErrorAction SilentlyContinue)
if ($explorerprocesses.Count -eq 0) {
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME;
Username = [string]::Empty
LoggedOnSince = [string]::Empty
}
} else {
foreach ($i in $explorerprocesses) {
$Username = $i.GetOwner().User
$Domain = $i.GetOwner().Domain
New-Object -TypeName PSObject -Property #{
ComputerName = $env:COMPUTERNAME ;
Username = '{0}\{1}' -f $Domain,$Username ;
LoggedOnSince = ($i.ConvertToDateTime($i.CreationDate)) ;
}
}
}
} # endof scriptblock
# 2. Create an empty array to store results
$results = #()
# 3. Query target computers using PSRemoting
Get-content "C:\Computers.txt" | ForEach-Object -Process {
$computer = $_
try {
$results += Invoke-Command -ComputerName $Computer -ScriptBlock $SB -ErrorAction Stop
} catch {
Write-Warning -Message "Faild to use PSremoting on $Computer because $($_.Exception.Message)"
}
}
# 4. Display the results
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize
# 5. Send results to a text file
$results | Select ComputerName,Username,LoggedOnSince | ft -AutoSize | Out-File -FilePath "C:\LoggedOnResults.txt"
I hope someone can help me with this. We want to see which computers have a HDD and SDD. I have an excel.csv of the computers. I import the computers. But when I export them I never see the csv or its incomplete. Can you tell what part of my script is incorrect. Thank you
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers) {
Write-Host "`nPulling Physical Drive(s) for $computer"
if((Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet)){
Invoke-Command -ComputerName $computer -ScriptBlock {
Get-WmiObject -Class MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage | Select-Object sort -Property PSComputerName, Model, SerialNumber, MediaType
Export-Csv C:\Temp\devices.csv
}
}
}
Update: 11/11/2021
Thank you everyone for you help
This script worked for me:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\Computers.csv"
{} | Select "ComputerName", "Status", "Model", "SerialNumber", "MediaType" | Export-Csv $ExportTo
$data = Import-csv -path $ExportTo
foreach ($computer in $computers) {
$Online = Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer.computer -Quiet
if ($Online) {
Write-Host $computer.computer " is Online"
$OutputMessage = Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer.computer | Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType
$data.ComputerName = $computer.computer
$data.Status = $OutputMessage.Status
$data.Model = $OutputMessage.Model
$data.SerialNumber = $OutputMessage.SerialNumber
$data.MediaType = $OutputMessage.MediaType
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
} else {
Write-Host $computer.computer " is Offline"
$data.ComputerName = $computer.computer
$data.Status = "Offline"
$data.Model = ""
$data.SerialNumber = ""
$data.MediaType = ""
$data | Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Continuing from my comment. . . as is, you would be exporting the results to the remote machine. That's if it was piped properly. You're currently missing a pipe (|) before Export-Csv.
Also, there's no need to invoke the command, as Get-WMIObject has a parameter for remote computers: -ComputerName. It's also a deprecated cmdlet that has been replaced by Get-CimInstance.
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
Write-Host "`nPulling Physical Drive(s) for $computer"
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
}
}
Side Note: Get-CimInstance accepts an array of strings, meaning you can pass the entirety of $Computers to it. This should allow it to perform the the query in parallel, vs serial (one at a time):
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computers -ErrorAction SilentlyContinue |
Select-Object -Property PSComputerName, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation
Performing queries one at a time doesn't necessarily mean it's bad. You can actually have more control over the control of flow for your script.
EDIT:
Following up on your comment...you're no longer using your if statement to check if the computer is online before connecting. So given that you keep the if statement, and add an else condition, you can create a calculated property to add another property to export of Status. Then, you can pass it a value of Online, or Offline depending on if the machine is online or not:
$ExportTo = "C:\Temp\devices.csv"
$computers = Import-csv -path "C:\Temp\MediaType\Computers.csv"
foreach ($computer in $computers)
{
if (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computer -Quiet) {
Write-Host -Object "`nPulling Physical Drive(s) for $computer"
Get-CimInstance -ClassName MSFT_PhysicalDisk -Namespace root\Microsoft\Windows\Storage -ComputerName $computer |
Select-Object -Property PSComputerName,#{N='Status';E={'Online'}}, Model, SerialNumber, MediaType |
Export-Csv -Path $ExportTo -Append -NoTypeInformation -Force
}
else {
Write-Host -Object "`n$Computer is Offline"
[PSCustomObject]#{PSComputerName=$Computer;Status='Offline'} | Export-Csv -Path $ExportTo -Append -Force
}
}
Also:
Always remember that even if you can ping a machine, it doesn't mean you can connect to it.
This can be mitigated by using a CIM Session, or PSSession depending on the type of commands you're running.
To specifically answer the question:
How do I correctly export a CSV file (use Export-Csv)?
You might want to read about PowerShell pipelines and PowerShell cmdlets.
Basically, a cmdlet is a single command that participates in the pipeline semantics of PowerShell. A well written cmdlet is implemented for the Middle of a Pipeline which means that it processes ("streams") each individual item received from the previous cmdlet and passes it immediately to the next cmdlet (similar to how items are processed in an assembly line where you can compare each assembly station as a cmdlet).
To better show this, I have created an easier minimal, complete and verifiable example (MVCE) and replaced your remote command (Invoke-Command ...) which just an fake [pscustomobject]#{ ... } object.
With that;
I have used Get-Content rather then Import-Csv as your example suggest that Computers.csv is actually a text file which list of computers and not a Csv file which would require a (e.g. Name) header and using this property accordingly (like $Computer.Name).
To enforce the pipeline advantage/understanding, I am also using the ForEach-Object cmdlet rather than the foreach statement which is usually considered faster but this is probably not the case here as for the foreach statement it is required to preload all $Computers into memory where a well written pipeline will immediately start processing each item (which in your case happens on a remote computer) while still retrieving the next computer name from the file.
Now, coming back on the question "How do I correctly export a CSV file" which a better understanding of the pipeline, you might place Export-Csv within the foreach loop::
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
} |Export-Csv .\Devices.csv -Append
}
As commented by #lit, this would require the -Append switch which might not be desired as every time you rerun your script this would append the results to the .\Devices.csv file.
Instead you might actually want do this:
Get-Content .\Computers.txt |ForEach-Object {
[pscustomobject]#{
PSComputerName = $_
Model = "Model"
SerialNumber = '{0:000000}' -f (Get-Random 999999)
MediaType = "MydiaType"
}
} |Export-Csv .\Devices.csv
Note the differences: the Export-Csv is placed outside the loop and the -Append switch is removed.
Explanation
As with e.g. the ForEach-Object cmdlet, the Export-Csv cmdlet has internally Begin, Process and End blocks.
In the Begin block (which runs when the pipeline is started), the Export-Csv cmdlet prepares the csv file with a header row etc. and overwrites any existing file.
In the Process block (which runs for each item received from the pipeline) it appends each line (data record) to the file.
I found a script that logs all users of RDS servers which works great;
Link here
However I want to make it specific for 1 user, not all users.
I really don't know powershell so need some help.
Param(
[array]$ServersToQuery = (hostname),
[datetime]$StartTime = "January 1, 1970"
)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = (get-date).adddays(-7)
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
[array]$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "."
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
So, you say …
(I really don't know powershell so need some help.)
..., but point to a very advanced PowerShell script you want to use.
It is vital that you do not use anyone's code that you do not fully understand what it is doing from anyone. You could seriously damage / compromise your system(s) and or you entire enterprise. Please ramp up to protect yourself, your enterprise and avoid unnecessary confusion, complications, issues, errors and frustration you are going to encounter:
Follow this link
As for your query...
However I want to make it specific for 1 user, not all users.
… Though the script returns all users, you can just filter / prompt for the one user you are after, without changing anything about the authors code.
Prompt for a user by adding an additional parameter in that param block
[string]$targetUser = (Read-Host -Prompt 'Enter a username')
In that $FilteredOutput section, is where you'd use the additional $targetUser parameter, using the Where-Object cmdlet or string matching there or in the ….
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
… section. Something like...
($FilteredOutput -match $TargetUser) | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
I do not have an environment to test this, so, I'll leave that up to you.
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
This is all basic PowerShell 'using parameters' use case, and covered in all beginning PowerShell courses, books, websites, and built-in PowerShell help files.
I wrote a PowerShell script that gets some cluster information. One of the columns I need is from the first argument in the pipeline and I can't find a way to return it's value.
function Get-SQL-Clusters {
Param([string]$server)
$servers = Get-Content -LiteralPath "C:\temp\sql_clusters.txt"
if ($server -ne 1) {
$files = foreach ($box in $servers) {
Invoke-Command -ComputerName $box {
Get-ClusterResource | Get-ClusterParameter
} | Where-Object {
$_.Name -eq "Address"
} | Format-Table PSComputerName, ClusterObject, State, Name, Value -AutoSize
}
} else {
Write-Warning "'$server' is not a valid path."
}
return $files
}
When I run this, I get the data I need but State is blank. It's in Get-ClusterResource, but the IP, which is what I'm mostly looking for, is in Get-ClusterParameter.
Ideally I would like to return the name of the cluster, each of the alwayson names, it's IP and it's current state so I can see if the active IP is on the primary site or on the DR site.
Your call to Invoke-Command places the Get-ClusterResource | Get-ClusterParameter calls into its own script block {...}, then pipes the results of evaluating those expressions to the Where-Object cmd. This may not be the intended order of operations.
Project your results using the Select-Object cmdlet at intermediate places in your pipeline to give you access to the desired properties at later stages (specific syntax hasn't been checked;YMMV):
Invoke-Command -ComputerName $box { Get-ClusterResource | Select-Object -Property State, #{Name="ClusterParameter";Expression = {(Get-ClusterParameter -InputObject $_) }}| Where-Object { $_.ClusterParameter.Name -eq ...
Will produce objects like:
State | ClusterParameter
------------------------
foo ClusterParameter.ToString()
The almost final code. It's not 100% complete but I get the State and IP values now and will fix the rest later. Another change I made was to stop using text files and created a hash table for my servers because of formatting problems I had with text files.
function Get-SQL-Clusters-scrap
{
param([string]$server)
import-module c:\temp\sql_hashtable2.ps1
$servers = $sql_servers.hadr
if ($server -ne 1)
{
$files = ForEach ($box in $servers) {invoke-command -ComputerName $box {Get-ClusterResource |
foreach-object {($state) = ($_.State); $_ |
get-clusterparameter |Where-Object {$_.Name -eq "Address"} |
Format-Table ClusterObject,#{Name=”State”;Expression={$state}}, Name, Value, PSComputerName -AutoSize}}}
}
else
{Write-Warning "'$server' is not a valid path."}
return $files
}
So I have this script working except I cannot get the computer name to display when there is an error, I understand that is because the computer is offline and the first Get computername cmd is an error as well, but I have tried multiple ways to perform this function with one thing or another not working.
gc "\\server\s$\powershellscripts\LabMachines\*.txt" | ForEach-Object{
Try{
$wdt = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $_ -Ea Stop
$cdt = Get-Content "\\$_\C$\file\*.txt" -Ea Stop
}
Catch{
$wdt= $_
$cdt = 'Offline or NoFile'
}
$wdt | Select #{n='WorkStation';e={$_.PSComputerName}},#{n='Version';e={$cdt}}
} | ogv -Title 'Versions'
$host.enternestedprompt()
In a different attempt I have used this script, but this one below formats the information in rows instead of column like the first one.
Get-Content "\\server\s$\powershellscripts\LabMachines\*.txt" | ForEach-Object {
Try{
#{ComputerName=$_}
#{Version = Get-Content "\\$_\C$\file\*.txt" -Ea Stop}
}
Catch{
#{Version = 'Offline or NoFile' }
}
} #| out-file \\localhost\c$\users\public\Desktop\version.csv
Any attempt to take my script and merge it with one someone suggested to me "the second one" breaks it completely. To reiterate the top script give me the format output in columns I want but the bottom script gives the catch error "ComputerName Error" I need but has the output in harder to read row outfile.
Any suggestions?
Thanks.
Inside the catch block, $_ no longer refers to the pipeline item, but to the error that was caught.
Assign $_ to a different variable and you can reuse it inside the catch block:
Get-Content "\\server\s$\powershellscripts\LabMachines\*.txt" | ForEach-Object{
Try{
$ComputerName = $_
$wdt = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Ea Stop
$cdt = Get-Content "\\$_\C$\file\*.txt" -Ea Stop
}
Catch{
$wdt = #{PSComputerName = $ComputerName}
$cdt = 'Offline or NoFile'
}
$wdt | Select #{n='WorkStation';e={$_.PSComputerName}},#{n='Version';e={$cdt}}
}
Pipe to Export-Csv at the end if you want to output it to a CSV file:
} |Export-Csv C:\users\public\Desktop\version.csv -NoTypeInformation
I so I wanted to throw my finished product with a few additional things I learned. Here it is.
Function Version {
$i=1
$LabMachines = "\\server\s$\powershellscripts\LabMachines\*.txt"
$Total = 107 #a manual number for now
Get-Content $LabMachines | ForEach-Object{
Try{
$ComputerName=$_
$wdt = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Ea Stop
$cdt = Get-Content "\\$_\C$\file\*.txt" -Ea Stop
}
Catch{
$wdt = #{PSComputerName = $ComputerName}
$cdt = 'Offline'
}
$wdt | Select #{n='WorkStation';e={$_.PSComputerName}},#{n='Version';e={if($cdt){$cdt}else{"Error with Version.txt"}}}
$i++
$percent = [Math]::Round($i / $Total*100) } | Tee-Object -file \\localhost\c$\users\public\desktop\versions.csv |
Select #{n='%Complete';e={if(!($null)){$percent}else{"NULL"}}},
#{n='Number';e={if(!($null)){$i}else{"NULL"}}},
#{n='Computer';e={if(!($null)){$ComputerName}else{"NULL"}}},
#{n='Version';e={if($cdt){$cdt}else{"Error with Version.txt"}}} }
The changes I made in addition to the answer above was it will show progress in the console as a percent, number, computername, version txt as it completes. Also I made this into a function so we can run it and get the .csv file on our own desktop. To be fancier you could put in a line to do a line count to achieve the manual number I am using.
Thanks so much for the help I learned alot.