PowerShell Exporting - powershell

Can someone point me in the right direction? Basically, I would like to export the results of my testpath to a csv. Below is what I am working with. I have read a couple Microsoft documents but they only seem to confuse me even more. Any feedback is appreciated.
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
Foreach ($Computer in $ComputerList)
{
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference= 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
}
$Output =New-Object -TypeName PSObject -Property #{
} | Select-Object
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"

Assuming you want a record per user per computer, there's two things you want to change structurally:
Create new objects in the inner foreach loop
Assign all the objects created to $Output:
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
$Output = Foreach ($Computer in $ComputerList) {
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference = 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
New-Object -TypeName PSObject -Property #{
# We still need a bit of magic here
}
}
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Now we just need to decide on what properties to add to our output objects:
New-Object -TypeName PSObject -Property #{
# We definitely want to know which computer and user profile the results are for!
ComputerName = $Computer
ProfileName = $user.Name
# And finally we want the results of `Test-Path`
Result = $t
}

Here's another option. Though nowhere near as elegant as what Matthias gave you. ;-}
It's just a refactor, to narrow down your code and pass everything directly and output by default, without the need for all the, Write-* stuff and the like. PowerShell just grants a number of ways to accomplish a use case.
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results on screen
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
104DB2FE-76B8-4 no
Localhost no
127.0.0.1 no
#>
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\MicrosoftEdge") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
ComputerName Status
------------ ------
104DB2FE-76B8-4 Has it
Localhost Has it
127.0.0.1 Has it
#>

Related

Powershell - trying to merge 2 result in 1 txt/csv

I'm trying to make a daily script to check status of list of URLS and pinging servers.
I've tried to combine the csv, however, the output of $status code is different from the one in csv
$pathIn = "C:\\Users\\test\\Desktop\\URLList.txt"
$URLList = Get-Content -Path $pathIn
$names = gc "C:\\Users\\test\\Desktop\\hostnames.txt"
#status code
$result = foreach ($uri in $URLList) {
try {
$res = Invoke-WebRequest -Uri $uri -UseBasicParsing -DisableKeepAlive -Method Head -TimeoutSec 5 -ErrorAction Stop
$status = [int]$res.StatusCode
}
catch {
$status = [int]$_.Exception.Response.StatusCode.value__
}
# output a formatted string to capture in variable $result
"$status - $uri"
}
$result
#output to log file
$result | Export-Csv "C:\\Users\\test\\Desktop\\Logs.csv"
#ping
$output = $()
foreach ($name in $names) {
$results = #{ "Host Name" = $name }
if (Test-Connection -Computername $name -Count 5 -ea 0) {
$results["Results"] = "Up"
}
else {
$results["Results"] = "Down"
}
New-Object -TypeName PSObject -Property $results -OutVariable nameStatus
$output += $nameStatus
}
$output | Export-Csv "C:\\Users\\test\\Desktop\\hostname.csv"
#combine the 2 csvs into 1 excel file
$path = "C:\\Users\\test\\Desktop" #target folder
cd $path;
$csvs = Get-ChildItem .\*.csv
$csvCount = $csvs.Count
Write-Host "Detected the following CSV files: ($csvCount)"
foreach ($csv in $csvs) {
Write-Host " -"$csv.Name
}
Write-Host "--------------------"
$excelFileName = "daily $(get-Date -Format dd-MM-yyyy).xlsx"
Write-Host "Creating: $excelFileName"
foreach ($csv in $csvs) {
$csvPath = ".\" + $csv.Name
$worksheetName = $csv.Name.Replace(".csv", "")
Write-Host " - Adding $worksheetName to $excelFileName"
Import-Csv -Path $csvPath | Export-Excel -Path $excelFileName -WorkSheetname $worksheetName
}
Write-Host "--------------------"
cd $path;
Get-ChildItem \* -Include \*.csv -Recurse | Remove-Item
Write-Host "Cleaning up"
Output in PowerShell
200 - https://chargebacks911.com/play-404/
200 - https://www.google.com/
500 - httpstat.us/500/
Host Name Results
----------------
x.x.x.x Down
x.x.x.x Up
Detected the following CSV files: (2)
- daily 26-03-2022.csv
- Logs.csv
--------------------
Creating: daily26-03-2022.xlsx
- Adding daily 26-03-2022 to daily26-03-2022.xlsx
- Adding Logs to daily26-03-2022.xlsx
--------------------
Cleaning up
\----------------------------------
result in excel
\#Hostname
Host Name Results
x.x.x.x Down
x.x.x.x Up
\#Logs
Length
42
29
22
I would like to know
how to correct the output in "Logs" sheet
if there's anyway to simplify my script to make it cleaner
Welcome to SO. You're asking for a review or refactoring of your complete script. I think that's not how SO is supposed be used. Instead you may focus on one particular issue and ask about a specific problem you have with it.
I will focus only on the part with the query of the status of your servers. You should stop using Write-Host. Instead you should take advantage of PowerShells uinique feature - working with rich and powerful objects instead of stupid text. ;-)
I'd approach the task of querying a bunch of computers like this:
$ComputernameList = Get-Content -Path 'C:\Users\test\Desktop\hostnames.txt'
$Result =
foreach ($ComputerName in $ComputernameList) {
[PSCustomObject]#{
ComputerName = $ComputerName
Online = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet
}
}
$result
Now you have a PowerShell object you can pipe to Export-Csv for example or use it for further steps.
For example filter for the offline computers:
$result | Where-Object -Property Online -NE -Value $true
If you insist to have a visual control during the runtime of the script you may use Write-Verbose or Write-Debug. This way you can switch on the output if needed but omit it when the script runs unattended.

CSV Output Unreadable

I'm trying to export the results of this simple script to a .csv file. I get the results but it either returns information about data of the results or a long jumble of data I'm sure how to parse correctly.
<#
Will ping all devices in list and display results as a simple UP or DOWN, color coded Red or Green.
#>
$Names = Get-Content -Path "C:\TestFolder\GetNames.txt"
$Output = #()
foreach ($name in $names)
{
if (Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue)
{
$Result1 += "$name, up"
Write-Host "$name, up" -ForegroundColor Green
}
else
{
$Result2 += "$name, down"
Write-Host "$name, down" -ForegroundColor Red
}
}
$Output += $Result1, $Result2
$Output = $Output | Select-Object
$Output | Export-Csv -Path 'C:\psCSVFiles\mycsv.csv' -NoTypeInformation
Results of this return:
Length
49768
25081
What am I doing wrong here? Thanks
Don't attempt to "format" the output strings manually - Export-Csv will take care of that part.
What you want to do is create objects with properties corresponding to the columns you want in your CSV:
$Names = Get-Content -Path "C:\TestFolder\GetNames.txt"
$Output = foreach ($name in $names) {
# test if computer is reachable/pingable
$isUp = Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue -Quiet
# construct output object with results
[pscustomobject]#{
ComputerName = $name
Status = if($isUp){ 'Up' }else{ 'Down' }
}
}
# Export to CSV
$Output |Export-Csv -Path 'C:\psCSVFiles\mycsv.csv' -NoTypeInformation
Alternatively, use Select-Object to modify the input strings directly using the pipeline:
Get-Content -Path "C:\TestFolder\GetNames.txt" |Select #{Name='ComputerName';Expression={$_}},#{Name='Status';Expression={if(Test-Connection -ComputerName $_ -Count 1 -ErrorAction SilentlyContinue -Quiet){'Up'}else{'Down'}}} |Export-Csv -Path 'C:\psCSVFiles\mycsv.csv' -NoTypeInformation

Powershell: Trying to locate file in multiple drives from list of servers

I'm trying (and failing) to:
Connect to the server by iterating through a list.
Confirm location where file exists (1 of 3 locations).
Replace a string in that file.
I've tried to do this multiple ways. There are two that I have which do part of what I want.
Can someone please help me understand if there's something I'm doing inefficiently or how to put all this together?
This one can loop through the servers and find the file
$ErrorActionPreference = 'SilentlyContinue'
$nope=$null
$servers= Get-Content C:\Servers.txt
foreach ($server in $servers)
{
If (Test-Connection -ComputerName $server -Quiet)
{Invoke-Command -ComputerName $server -ScriptBlock {$file=(Get-Childitem -Path C:\DiskSpace.ps1, D:\DiskSpace.ps1, Y:\DiskSpace.ps1); Write-Host "Found $file on $env:computername."}}
Else {
Write-Host ">> Could not connect to $server."; $nope += $server}
}
Write-Host $nope
...and this one can at least find a local file
$valid=#('')
$paths = #("C:\Users\user_name\Desktop\DiskSpace.ps1","C:\DiskSpace.ps1","D:\DiskSpace.ps1","Y:\DiskSpace.ps1")
Foreach ($path in $paths)
{
if (Test-Path $path)
{$valid += $path}
}
write-host $valid
Here's how I intend to to replace the string:
$ErrorActionPreference = 'SilentlyContinue'
$find=(Get-Childitem -Path C:\, D:\, Y:\ -include DiskSpace.ps1 -Recurse)
Write-Host $find
$ErrorActionPreference = 'Stop'
try {
(Get-Content $find).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $find
}
catch {
}
Get-Content $find
You had all the pieces already. Simply loop over your Get-Content command for each file in the Invoke-Command.
$ErrorActionPreference = 'SilentlyContinue'
$servers = Get-Content C:\Servers.txt
$files = #('C:\DiskSpace.ps1', 'D:\DiskSpace.ps1', 'Y:\DiskSpace.ps1')
$report = foreach ($server in $servers) {
if (Test-Connection -ComputerName $server -Quiet) {
$response = Invoke-Command -ComputerName $server -ScriptBlock {
Get-Childitem -Path $using:files | ForEach-Object {
(Get-Content $_).replace('bad_email#domain.com', 'good_email#domain.com') | Set-Content $_
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "$($_.fullname) updated."
}
}
}
if ($response -eq $null) {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "No files found"
}
} else {
$response
}
} else {
[PSCustomObject]#{
Name = $env:COMPUTERNAME
Message = "Unreachable"
}
}
}
$report

Invoke-Command: Null-value and positional parameter errors

I wanted to start a new thread for this, since I am using a different method in my code now. I have written a script that pings hundreds of devices and logs their online or offline status. It was taking an extremely long time to run, so I am now looking into using Invoke-Command to run the commands remotely on servers for each site (instead of all from the same server). I am receiving the following errors: "A positional parameter cannot be found that accepts argument 'Of'", "You cannot call a method on a null-valued expression", and this is happening for each server as it is iterating through them. Any ideas as to why this is happening? Thank you very much, here is my current code:
<#
.NOTES
===========================================================================
Created on: 11/17/2016 8:06 AM
Created by:
Organization:
Filename: Get-MPOSOfflinePrinters.ps1
===========================================================================
.DESCRIPTION
#>
#Define log file variables and remove any existing logs
$logfile = "D:\Logs\MPOSPrinterPingLog.txt"
$offlineprinters = "D:\Reports\MPOS\MPOSOfflinePrinters.txt"
If (Test-Path $logfile) {Remove-Item $logfile}
If (Test-Path $offlineprinters) {Remove-Item $offlineprinters}
Add-Content $logfile "Gathering server list"
#Compiling list of all MPOS Print Servers
$serverList = (Get-ADComputer -Filter "Name -like 'Q0*P30' -or Name -like 'Q0*P32'" -SearchBase "OU=Print,OU=Prod,OU=POS,DC=COMPANY,DC=NET").name | Sort-Object | Out-File C:\Temp\MPOS\MPOSPrintServers.txt
$serverListPath = "C:\Temp\MPOS\MPOSPrintServers.txt"
Add-Content $logfile "Compiling text file"
#Retrieve a list of MPOS Print servers from text file and set to variable $serverNames
$serverNames = Get-Content -Path $serverListPath
Invoke-Command -ComputerName $serverNames -ScriptBlock {
#Define log file variables and remove any existing logs
$logfile = "C:\Temp\MPOSPrinterPingLog.txt"
$offlineprinters = "C:\Temp\MPOSOfflinePrinters.txt"
$masteroffline = "\\a0345p689\d$\Reports\MPOS\MPOSOfflinePrinters.txt"
If (Test-Path $logfile) {Remove-Item $logfile}
If (Test-Path $offlineprinters) {Remove-Item $offlineprinters}
#process xml file to parse IP addresses for ping
$timestamp2 = (Get-Date -Format g)
Add-Content $logfile "$timestamp2 - Processing xml file from $serverName to parse data to csv"
$xml = [xml](Get-Content C:\ProgramData\Microsoft\Point Of Service\Configuration\Configuration.xml)
$PrinterNames = $xml.selectNodes('//PointOfServiceConfig/ServiceObject/Device') | foreach {New-Object -TypeName psobject -Property #{LogicalName=$_.LogicalName.Name}}
$PrinterIPs = $xml.selectNodes('//PointOfServiceConfig/ServiceObject/Device') | foreach {New-Object -TypeName psobject -Property #{HardwarePath=$_.HardwarePath}}
foreach ($PrinterIP in $PrinterIPs) {
$pingableIP = $PrinterIP.HardwarePath
If (Test-Connection $pingableIP -Quiet -Count 1) {
$timestamp3 = (Get-Date -Format g)
Add-Content $logfile "$timestamp3 - $serverName, $pingableIP is online and pingable"
}
Else {
$timestamp3 = (Get-Date -Format g)
Add-Content $offlineprinters "$timestamp3 - $serverName, $pingableIP is offline!"
}
Get-Content $offlineprinters | Out-File -FilePath $masteroffline -Append -NoClobber
} #foreach ($PrinterIP in $PrinterIPs) {
} #Invoke-Command -ComputerName $serverNames -ScriptBlock {

Powershell Read-host after Write-host

I'm stuck in a probably dumb problem :(
Basically I have a function that write some output, and I would wait before exiting the script with a Read-Host command after the output of the function.
Here you are the code:
Function Get-FileMetaData {
<# modified script from:
NAME: Get-FileMetaData
AUTHOR: ed wilson, msft
LASTEDIT: 01/24/2014 14:08:24
Http://www.ScriptingGuys.com
Requires -Version 2.0
#>
Param($folders)
$tagList = #()
$tagListUnique = #()
foreach($folder in $folders) {
$i = 18 # attribute for Tags
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($folder.FullName)
foreach ($file in $objFolder.items()) {
if($objFolder.getDetailsOf($File, $i)) {
$objEntry = New-Object System.Object
$objEntry | Add-Member -type NoteProperty -name File -value $file.Path
$objEntry | Add-Member -type NoteProperty -name Tags -value $objFolder.getDetailsOf($File, $i)
$tagList += $objEntry
$tagListUnique += ($objFolder.getDetailsOf($File, $i) -split ";").trim()
}
}
}
Write-Output $tagList
Write-Output ""
Write-Output "unique tags"
Write-Output "-----------"
$tagListUnique | Sort-Object -unique
Read-Host "Press ENTER to exit"
}
$baseFolder = "C:\MyPictures"
Write-Host ""
Write-Host "Base folder: " $baseFolder
Get-FileMetaData -folder (Get-ChildItem $baseFolder -Recurse -Directory)
Basically it prints out the final statement "Press ENTER to exit" before the $tagList array.
I would like exactly the opposite, as in the order written in the code. With my limited ps skills, I understood there is something related in the different management of output "stream" and the input, but I cannot figure out how to "flush" all the output before it writes in the host.
Thanks in advance
Not sure what the technical difference is between Write-Host and Out-Host, but I got around it by piping the customobject ($oOptions) to Out-Host. For example:
Write-Output "Set subscription:"
$oSubscriptions = Get-AzureRmSubscription
If($Subscriptions.count -gt 1)
{
Write-Verbose "Multiple subscriptions found. "
$i=1
$oOptions = #()
$oSubscriptions | ForEach-Object{
$oOptions += [pscustomobject]#{
Item = $i
SubscriptionName = $_.SubscriptionName
}
$i++
}
$oOptions | Out-Host
$selection = Read-Host -Prompt "Please make a selection"
$Selected = $oOptions | Where-Object{$_.Item -eq $selection}
$ActiveSubscription = Select-AzureRmSubscription -SubscriptionName $Selected.SubscriptionName
$ActiveSubscription
Write-Output "Subscription '$($ActiveSubscription.Subscription.SubscriptionName)' active."
}
else
{
$Subscriptions | Select-AzureRmSubscription | Out-Null
}
Hope that helps.
Try this:
Function Get-FileMetaData {
Write-Output ([PSCustomObject]#{Value="this function is executed"}) | Out-String
}
Write-Output "Before function execution"
Get-FileMetaData
Read-Host "Press ENTER to continue"
Putting the Read-Host AFTER the execution of your function, will make it output everything to the console, THEN wait for the Read-Host to be done.
Result:
I've updated the code above to include the [PSCustomObject] to the Write-Output, and by adding a Out-String at the end, everything works fine for me. Please let me know if this works!