I am creating a script to get the uptime / last reboot of a group of PCs read in from a text file.
I can output the results to file, but it keeps repeating the column header which I do not want. Basically what I would like is to output the first line for the headers and then each line after for the data the data underneath. The code below repeats the column header to the output file and there are a lot of spaces between the lines. Would an output to CSV be easier to handle?
Here is my code. The first Out-File command is to overwrite the file if it exists, essentially clearing the file.
$computers = Get-Content "c:\temp\ps\pc.txt"
out-file -FilePath "C:\temp\ps\output.txt"
foreach ($computer in $computers)
{
$Computerobj = "" | select ComputerName, Uptime, LastReboot
$wmi = Get-WmiObject -ComputerName $computer -Query "SELECT LastBootUpTime FROM Win32_OperatingSystem"
$now = Get-Date
$boottime = $wmi.ConvertToDateTime($wmi.LastBootUpTime)
$uptime = $now - $boottime
$d =$uptime.days
$h =$uptime.hours
$m =$uptime.Minutes
$s = $uptime.Seconds
$Computerobj.ComputerName = $computer
$Computerobj.Uptime = "$d Days $h Hours $m Min $s Sec"
$Computerobj.LastReboot = $boottime
$Computerobj
out-file -FilePath "C:\temp\ps\output.txt" -in $computerobj -append
}
A pipeline with ForEach-Object and Export-Csv would be a better approach:
$now = Get-Date
Get-Content -Path 'C:\temp\ps\pc.txt' | ForEach-Object {
$wmi = Get-WmiObject -ComputerName $_ -Class Win32_OperatingSystem
$boottime = $wmi.ConvertToDateTime($wmi.LastBootUpTime)
$uptime = $now - $boottime
New-Object -Type PSObject -Property #{
'ComputerName' = $_
'Uptime' = '{0} Days {1} Hours {2} Min {3} Sec' -f $uptime.Days,
$uptime.Hours, $uptime.Minutes, $uptime.Seconds
'LastReboot' = $boottime
}
} | Export-Csv -Path 'C:\temp\ps\output.txt' -NoType
If you need the data both in a file and on the console, you could use ConvertTo-Csv and Tee-Object:
Get-Content 'c:\temp\ps\pc.txt' | ForEach-Object {
...
} | ConvertTo-Csv -NoType | Tee-Object -FilePath 'C:\temp\ps\output.txt'
Try this:
$computers = Get-Content "c:\temp\ps\pc.txt"
#Create a report variable as an array to hold all our data
$report = #();
#No longer necessary
#out-file -FilePath "C:\temp\ps\output.txt"
foreach ($computer in $computers)
{
$Computerobj = "" | select ComputerName, Uptime, LastReboot
$wmi = Get-WmiObject -ComputerName $computer -Query "SELECT LastBootUpTime FROM Win32_OperatingSystem"
$now = Get-Date
$boottime = $wmi.ConvertToDateTime($wmi.LastBootUpTime)
$uptime = $now - $boottime
$d =$uptime.days
$h =$uptime.hours
$m =$uptime.Minutes
$s = $uptime.Seconds
$Computerobj.ComputerName = $computer
$Computerobj.Uptime = "$d Days $h Hours $m Min $s Sec"
$Computerobj.LastReboot = $boottime
#Add the computer to the report array
$report += $Computerobj
}
#Uncomment this if you need to see the report as well as write it to a file
#Write-Output $report
out-file -FilePath "C:\temp\ps\output.txt" -in $report
Now you can manipulate the report as a whole, so you can even add things at the end like $report = $report | Sort-Object -Property ComputerName to sort the report by computer names, or filter it with Where-Object.
Related
I'm new to Powershell but I've given it my best go.
I'm trying to create a script to copy a file to the All Users Desktop of all the XP machines in an Array. The script basically says "If the machine is pingable, copy the file, if not, don't." I then want to export this information into a CSV file for further analysis.
I've set it all but no matter what I do, it will only export the last PC that it ran though. It seems to run through all the PC's (tested with outputting to a txt file) but it will not log all the machines to a CSV. Can anyone give any advise?
$ArrComputers = "PC1", "PC2", "PC3"
foreach ($Computer in $ArrComputers) {
$Reachable = Test-Connection -Cn $Computer -BufferSize 16 -Count 1 -ea 0 -quiet
$Output = #()
#Is the machine reachable?
if($Reachable)
{
#If Yes, copy file
Copy-Item -Path "\\servername\filelocation" -Destination "\\$Computer\c$\Documents and Settings\All Users\Desktop\filename"
$details = "Copied"
}
else
{
#If not, don't copy the file
$details = "Not Copied"
}
#Store the information from this run into the array
$Output =New-Object -TypeName PSObject -Property #{
SystemName = $Computer
Reachable = $reachable
Result = $details
} | Select-Object SystemName,Reachable,Result
}
#Output the array to the CSV File
$Output | Export-Csv C:\GPoutput.csv
Write-output "Script has finished. Please check output files."
The problem is this:
#Store the information from this run into the array
$Output =New-Object -TypeName PSObject -Property #{
SystemName = $Computer
Reachable = $reachable
Result = $details
} | Select-Object SystemName,Reachable,Result
}
#Output the array to the CSV File
$Output | Export-Csv C:\GPoutput.csv
Each iteration of your foreach loop saves to $Output. Overwriting what was there previously, i.e., the previous iteration. Which means that only the very last iteration is saved to $Output and exported. Because you are running PowerShell v2, I would recommend saving the entire foreach loop into a variable and exporting that.
$Output = foreach ($Computer in $ArrComputers) {
New-Object -TypeName PSObject -Property #{
SystemName = $Computer
Reachable = $reachable
Result = $details
} | Select-Object SystemName,Reachable,Result
}
$Output | Export-Csv C:\GPoutput.csv
You would want to append the export-csv to add items to the csv file
Here is an example
foreach ($item in $ITGlueTest.data)
{
$item.attributes | export-csv C:\organization.csv -Append
}
Here you go. This uses PSCustomObject which enumerates data faster than New-Object. Also appends to the .csv file after each loop so no overwriting of the previous data.
foreach ($Computer in $ArrComputers) {
$Reachable = Test-Connection -Cn $Computer -BufferSize 16 -Count 1 -ea 0 -quiet
#Is the machine reachable?
if($Reachable)
{
#If Yes, copy file
Copy-Item -Path "\\servername\filelocation" -Destination "\\$Computer\c$\Documents and Settings\All Users\Desktop\filename"
$details = "Copied"
}
else
{
#If not, don't copy the file
$details = "Not Copied"
}
#Store the information from this run into the array
[PSCustomObject]#{
SystemName = $Computer
Reachable = $reachable
Result = $details
} | Export-Csv C:\yourcsv.csv -notype -Append
}
I am trying to make a powershell script that gets computer names from a txt file, checks the registry to see what the current version of Flash is installed, and if it is less than 18.0.0.203, run an uninstall exe. Here is what I have been trying:
# Retrieve computer names
$Computers = Get-Content C:\Users\araff\Desktop\FlashUpdater\Servers.txt
# Select only the name from the output
#$Computers = $Computers | Select-Object -ExpandProperty Name
#Sets command to execute if the version is not 18.0.0.203
$command = #'
cmd.exe /C uninstall_flash_player.exe -uninstall
'#
#Iterate through each computer and execute the command if the version is not 18.0.0.203
[Array]$Collection = foreach ($Computer in $Computers){
$AD = Get-ADComputer $computer -Properties LastLogonDate
$ping = Test-Connection -quiet -computername $computer -Count 2
$datetime = Get-Date
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computer)
$RegKey= $Reg.OpenSubKey("SOFTWARE\Macromedia\FlashPlayerActiveX")
$version = $RegKey.GetValue("Version")
if ($version -eq '= 18.0.0.203') {
$installed = "Flash is up to date!"
}
Else {
$installed = "Removing old version..."
Invoke-Expression -Command:$command
}
New-Object -TypeName PSObject -Property #{
TimeStamp = $datetime
ComputerName = $computer
Installed = $installed
OnlineStatus = $ping
LastLogonDate = $AD.LastLogonDate
} | Select-Object TimeStamp, ComputerName, Installed, OnlineStatus, LastLogonDate
}
#Exports csv
$Collection | Export-Csv FlashUpdaterOutput.csv -NoTypeInformation
It exports the CSV just fine, but all the installed columns say "Removing" even if it is the current version. Any ideas on what I am doing wrong? Thanks!
Rather than opening a remote registry key and running cmd /c why not make a [scriptblock] and pipe it to Invoke-Command.
AsJob it and come back for the results later.
[scriptblock]$code = {
$uninst = gci "C:\Windows\System32\Macromed\Flash" -Filter "*.exe" | ?{ $_.Name -ne "FlashUtil64_18_0_0_204_ActiveX.exe" -and $_.Name -like "FlashUtil64_18_0_0_*_ActiveX.exe" }
foreach($flash in $uninst) {
Write-Host $flash.FullName
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo.FileName = $flash.FullName
$proc.StartInfo.Arguments = "-uninstall"
$proc.Start()
$proc.WaitForExit()
Write-Host ("Uninstalling {0} from {1}" -f $flash.BaseName,$env:COMPUTERNAME)
}
}
Invoke-Command -ScriptBlock $code -ComputerName frmxncsge01 -AsJob
Then later just come back and Get-Job | Receive-Job -Keep and see the results.
The code below outputs information like:
System Information for: Localhost
Model : {0}
Serial Number : {1}
Version : {2}
Monitor Model : {3}
Monitor Serial : {4}
How do I export to CSV and have the formatting in Excel like:
Name, Model, Serial Number, Version, Monitor Model, Monitor serial
I would like each value in its own cell.
Code 1:
$ArrComputers = "localhost"
$OutputLog = ".\output.log"
$NotRespondingLog = ".\notresponding.log"
$ErrorActionPreference = "Stop"
Clear-Host
ForEach ($Computer in $ArrComputers) {
try {
$computerSystem = get-wmiobject Win32_ComputerSystem -Computer $Computer
$computerBIOS = get-wmiobject Win32_BIOS -Computer $Computer
$Version = Get-WmiObject -Namespace "Root\CIMv2" `
-Query "Select * from Win32_ComputerSystemProduct" `
-computer $computer | select -ExpandProperty version
$MonitorInfo = gwmi WmiMonitorID -Namespace root\wmi -computername $computer |
Select -last 1 #{n="Model"; e={[System.Text.Encoding]::ASCII.GetString($_.UserFriendlyName -ne 00)}},
#{n="Serial Number";e={[System.Text.Encoding]::ASCII.GetString($_.SerialNumberID -ne 00)}}
} catch {
$Computer | Out-File -FilePath $NotRespondingLog -Append -Encoding UTF8
continue
}
$Header = "System Information for: {0}" -f $computerSystem.Name
write-host $Header -BackgroundColor DarkCyan
$Header | Out-File -FilePath $OutputLog -Append -Encoding UTF8
$Output = (#"
-------------------------------------------------------
Model : {0}
Serial Number : {1}
Version : {2}
Monitor Model : {3}
Monitor Serial : {4}
-------------------------------------------------------
"#) -f -join $computerSystem.Model, $computerBIOS.SerialNumber, $Version, `
$MonitorInfo.Model, $MonitorInfo."Serial Number"
Write-Host $Output
$Output | Out-File -FilePath $OutputLog -Append -Encoding UTF8
}
Drop the format string and simply export the data to a CSV:
$data = ForEach ($Computer in $ArrComputers) {
try {
...
} catch {
...
}
$props = [ordered]#{
'Name' = $computerSystem.Name
'Model' = $computerSystem.Model
'Serial Number' = $computerBIOS.SerialNumber
'Version' = $Version
'Monitor Model' = $MonitorInfo.Model
'Monitor Serial' = $MonitorInfo."Serial Number"
}
New-Object -Type PSCustomObject -Property $props
}
$data | Export-Csv 'C:\path\to\output.csv' -NoType
The New-Object statement is required, because Export-Csv exports the properties of a list of objects as the fields of the CSV file.
Beware that Excel is rather particular about what it accepts as CSV. The file must must be comma-separated (regardless of what field separator is configured in your system's regional settings).
All, I would like to know what is wrong with the following.
I am not interested in output to html or changing output format
$servers = Get-Content "c:\x\servers.txt";
$File = "C:\x\serverslowdrivespace.txt"
$datetime = Get-Date -Format "MM-dd-yyyy_HH:mm";
Write-Host $datetime
$datetime | Out-File $File
"`n" | Out-File $File -Append
foreach ($server in $servers)
{
#Write-Host $server
$server | Out-File $File -Append
Get-WmiObject -query "SELECT * from Win32_logicaldisk WHERE DriveType ='3'
-AND (($_.freespace/$_.Size)*100) -le 10)" | Format-Table DeviceID, #{ Name
= "Size(GB)" ; Expression = { [decimal] ( "{0:N0}" -f ($_.size /
1gb) ) } }, #{ Name = "Free Space(GB)" ; Expression = { [decimal]
( "{0:N0}" -f ($_.freespace / 1gb) ) } }, #{ Name = "Free (%)" ; Expression
= { "{0,6:P0}" -f (($_.freespace / 1gb) / ( $_.size / 1gb )) } } -AutoSize
| Out-File $File -Append
}
I think the issue you are having at this point is that you are using $drive, but $drive is never defined, because it just doesn't work that way. I don't know of a way to make a WMI query include a calculated result as a part of it's WHERE clause. Instead what I would suggest you do is pipe all of your drivetype='3' drives to a Where clause in PowerShell. That way you can filter it down to just the drives with low free space. In the following suggestion I create custom objects with the calculated properties that you created in your Format-Table selections, just to try and keep things clean. If you have issues try removing the line breaks after the pipes, I don't know if that's PSv2 friendly.
$servers = Get-Content "c:\x\servers.txt"
$File = "C:\x\serverslowdrivespace.txt"
$datetime = Get-Date -Format "MM-dd-yyyy_HH:mm"
Write-Host $datetime
$datetime | Out-File $File
"`n" | Out-File $File -Append
foreach ($server in $servers)
{
$server | Out-File $File -Append
Get-WmiObject -ComputerName $server -query "SELECT * from Win32_logicaldisk WHERE DriveType ='3'" | #Query server for hard drives
Where{(($_.freespace/$_.Size)*100) -lt 10} | #Filter for drives with less than 10% free space
ForEach{New-Object PSObject -Property #{
"DeviceID" = $_.DeviceID
"Size(GB)" = [decimal] ( "{0:N0}" -f ($_.size / 1gb) )
"Free Space(GB)" = [decimal] ( "{0:N0}" -f ($_.freespace / 1gb) )
"Free (%)" = "{0,6:P0}" -f (($_.freespace / 1gb) / ( $_.size / 1gb ))
}
} |
Format-Table -AutoSize | Out-File $File -Append
}
foreach ($computer in $computerlist) {
if((Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
foreach ($file in $REMOVE) {
Remove-Item "\\$computer\$DESTINATION\$file" -Recurse
Copy-Item E:\Code\powershell\shortcuts\* "\\$computer\$DESTINATION\"
}
} else {
Write-Host "\\$computer\$DESTINATION\"
}
}
I want to export Write-Host "\$computer\$DESTINATION\" to the CSV files so I know which computers were offline when the script ran.
I am running this from a Windows 7 machine
This solution creates a psobject and adds each object to an array, it then creates the csv by piping the contents of the array through Export-CSV.
$results = #()
foreach ($computer in $computerlist) {
if((Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
foreach ($file in $REMOVE) {
Remove-Item "\\$computer\$DESTINATION\$file" -Recurse
Copy-Item E:\Code\powershell\shortcuts\* "\\$computer\$DESTINATION\"
}
} else {
$details = #{
Date = get-date
ComputerName = $Computer
Destination = $Destination
}
$results += New-Object PSObject -Property $details
}
}
$results | export-csv -Path c:\temp\so.csv -NoTypeInformation
If you pipe a string object to a csv you will get its length written to the csv, this is because these are properties of the string, See here for more information.
This is why I create a new object first.
Try the following:
write-output "test" | convertto-csv -NoTypeInformation
This will give you:
"Length"
"4"
If you use the Get-Member on Write-Output as follows:
write-output "test" | Get-Member -MemberType Property
You will see that it has one property - 'length':
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property System.Int32 Length {get;}
This is why Length will be written to the csv file.
Update: Appending a CSV
Not the most efficient way if the file gets large...
$csvFileName = "c:\temp\so.csv"
$results = #()
if (Test-Path $csvFileName)
{
$results += Import-Csv -Path $csvFileName
}
foreach ($computer in $computerlist) {
if((Test-Connection -Cn $computer -BufferSize 16 -Count 1 -ea 0 -quiet))
{
foreach ($file in $REMOVE) {
Remove-Item "\\$computer\$DESTINATION\$file" -Recurse
Copy-Item E:\Code\powershell\shortcuts\* "\\$computer\$DESTINATION\"
}
} else {
$details = #{
Date = get-date
ComputerName = $Computer
Destination = $Destination
}
$results += New-Object PSObject -Property $details
}
}
$results | export-csv -Path $csvFileName -NoTypeInformation
simply use the Out-File cmd but DON'T forget to give an encoding type:
-Encoding UTF8
so use it so:
$log | Out-File -Append C:\as\whatever.csv -Encoding UTF8
-Append is required if you want to write in the file more then once.
what you are searching for is the Export-Csv file.csv
try using Get-Help Export-Csv to see whats possible
also Out-File -FilePath "file.csv" will work
You can always use the
echo "Column1`tColumn2`tColumn3..." >> results.csv
You will need to put "`t" between the columns to separates the variables into their own column. Here is the way I wrote my script:
echo "Host`tState" >> results.csv
$names = Get-Content "hostlist.txt"
foreach ($name in $names) {
$count = 0
$count2 = 13490
if ( Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue ) {
echo "$name`tUp" >> results.csv
}
else {
echo "$name`tDown" >> results.csv
}
$count++
Write-Progress -Activity "Gathering Information" -status "Pinging Hosts..." -percentComplete ($count / $count2 *100)
}
This is the easiest way to me. The output I get is :
Host|State
----------
H1 |Up
H2 |UP
H3 |Down
You can play around with the look, but that's the basic idea. The $count is just a progress bar if you want to spice up the look