I have created a PowerShell script that retrieves the following;
RegKey value "Servername"
Regkey value "ServerListName"
Regkey Value "Version"
Wmi-Object "Computername"
I would like to run this against a list of computers and export the values to a csv file or append the source csv file.
I can get the information to display on the screen but am having difficulty exporting and adding Titles for each of the returned results.
Clear-Host
$computers = Get-Content C:\Tanium.csv
foreach ($computer in $computers)
{
$RegKey ="Software\WOW6432Node\Tanium\Tanium Client"
Cd hklm:\$RegKey
Get-WMIObject Win32_ComputerSystem | Select-Object -ExpandProperty name
Get-ItemPropertyValue -Path. -Name ServerName
Get-ItemPropertyValue -Path. -Name ServerNameList
Get-ItemPropertyValue -Path. -Name Version
}
I would like to either export or append the existing csv file displaying data as follows if possible;
ComputerName: 123455
Version: 7.098
ServerName: 1233456454
ServerNameList: 1233456454
Assuming that PowerShell remoting is set up on all target computers:
$computers = Get-Content C:\Tanium.csv
Invoke-Command -Computer $computers {
$regKey = "HKLM:\Software\WOW6432Node\Tanium\Tanium Client"
$regKeyValues = Get-ItemProperty -LiteralPath $regKey
[pscustomobject] #{
ComputerName = (Get-CimInstance Cim_ComputerSystem).Name
Version = $regKeyValues.Version
ServerName = $regKeyValues.ServerName
ServerNameList = $regKeyValues. ServerNameList
}
} | Export-Csv -Append -NoTypeInformation -Encoding Utf8 -LiteralPath out.csv
Note:
I've switched from WMI to CIM cmdlets, given that CIM supersedes WMI in PSv3+.
The output order of the custom objects doesn't necessarily reflect the input order of computer names ($computers).
Related
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 currently use the following powershell script to export the list of all VMs on our network with their information and export into an excel file:
#// Set CSV file name
$uDateTime = Get-Date -f "yyyy-MM"
$uCSVFile = "C:\Servers"+$uDateTime+".csv"
#//Export out to csv file.
Get-ADComputer -filter * -Properties ipv4Address, OperatingSystem,DistinguishedName |
select-object Name, ipv4Address, OperatingSystem, #{label='OU';expression=
{$_.DistinguishedName.Split(',')[1].Split('=')[1]}} |
export-csv -path $uCSVFile
The excel content would look something like this:
I want to add another column to indicate if specific application exists on each server or not like this one
Upon googling around I see that I can utilize the Get-ItemProperty to read the registry in order to check if certain program is installed on individual VM, but I am having problem tying the code to my existing one. It gives me the same result based on the machine where this PowerShell scripts runs on instead of each VM registry individually ...
Can you help me making this script read each VM's registry
#// Set CSV file name
$uDateTime = Get-Date -f "yyyy-MM"
$uCSVFile = "C:\Servers"+$uDateTime+".csv"
#//Export out to csv file.
Get-ADComputer -filter * -Properties ipv4Address, OperatingSystem,DistinguishedName |
select-object Name, ipv4Address, OperatingSystem, #{label='OU';expression=
{$_.DistinguishedName.Split(',')[1].Split('=')[1]}},
#{label='HelloKitty Installed';expression={(Get-ItemProperty "HKLM:\Software\HelloKitty\*" | Where {
$_.Version -ne $null }) -ne $null}}|
export-csv -path $uCSVFile
To read a registry key from the computer you are targetting instead of the computer the script is currently running from, you should use the Invoke-Command cmdlet.
However, keep in mind that Get-ADComputer can also list computers that are currently off-line, so I would suggest using a ForEach-Object loop which will give you a chance to test for that first.
Something like this:
#// Set CSV file name
$uCSVFile = 'C:\Servers{0:yyyy-MM}.csv' -f (Get-Date)
#//Export out to csv file.
$result = Get-ADComputer -Filter * -Properties ipv4Address, OperatingSystem,DistinguishedName |
ForEach-Object {
if (Test-Connection -ComputerName $_.Name -Count 1 -Quiet) {
# computer is on line. If need be, add -Credential to the Invoke-Command cmdlet
# because reading the HKEY_LOCAL_MACHINE hive needs Administrator permissions.
# Also, the targetted machines must have the 'Remote Registry' service enabled.
try {
$installed = Invoke-Command -ComputerName $_.Name -ScriptBlock {
$null -ne (Get-ItemProperty "HKLM:\SOFTWARE\HelloKitty\*" |
Where-Object { $null -ne $_.Version }).Version
} -ErrorAction Stop
}
catch { $installed = "ERROR" }
}
else { $installed = "OFF-LINE" }
# output an object
$_ | Select-Object Name, ipv4Address, OperatingSystem,
#{Name = 'HelloKitty Installed'; Expression = { $installed }}
}
# now export to CSV
$result | Export-Csv -Path $uCSVFile -UseCulture -NoTypeInformation
I have added switch -UseCulture to the Export-Csv cmdlet so the delimiter character used in the csv file will be the same as your local Excel expects
I want to automate our auditing process, where we provide the local admin members, currently this is done with screenshots. I cobbled together the below code, which prompts for a server name and create a file with the local admin members. However this requires me to rename the file.
I want to be able to input the server name and have that also be the out-file name. I'm just not seeing the tree through the forest an how I go about it. Lots of stuff for appending file names but I didn't see anything for renaming a file you create.
Thanks
function get-localadmins {
[cmdletbinding()]
Param(
[string]$computerName
)
$group = get-wmiobject win32_group -ComputerName $computerName -Filter "LocalAccount=True AND SID='S-1-5-32-544'"
$query = "GroupComponent = `"Win32_Group.Domain='$($group.domain)'`,Name='$($group.name)'`""
$list = Get-WmiObject win32_groupuser -ComputerName $computerName -Filter $query
$list | % {$_.PartComponent} | % {$_.substring($_.lastindexof("Domain=") + 7).replace("`",Name=`"", "\")}
}
$Workstation = Read-Host "Computer Name"
get-localadmins $Workstation | Out-File c:\temp\ENTERSERVERNAME_LocalAdmin.txt
Try this out
| Out-File -FilePath "C:\Temp\${Workstation}_LocalAdmin.txt" -Append
How can i add the server name at the left of each line result on this script?. Thank you!
$servers = Get-Content -path .\Machines.txt
[pscustomobject]$result = #()
$subresult =
ForEach ($server in $servers)
{
Set-Service -computername $servers -Name sacsvr -StartupType Disabled -PassThru
}
$result = $subresult
$result | Out-File local_group_members.csv
This is an example result:
Status Name DisplayName
------ ---- -----------
Stopped sacsvr Special Administration Console Helper
Stopped sacsvr Special Administration Console Helper
Stopped sacsvr Special Administration Console Helper
Alternatively you can just add a property to the objects you're outputting right now. Pipe your Set-Service to Add-Member like this:
Set-Service -computername $servers -Name sacsvr -StartupType Disabled -PassThru | Add-Member -MemberType NoteProperty -Name 'Server' -Value $Server -PassThru
Now each object that you pass to $subresult has a new property Server that is the name of the server it was run on. You'll probably want to pipe through Select when outputting to have the order you want.
$SubResult | Select Server, Status, Name, DisplayName | Export-CSV 'local_group_members.csv' -NoType
You can arbitrarily re-order or add to your output with Select-Object. You can use hash tables to include calculated properties such as your desired ServerName.
So for each server, you can set the services and tag the output with that server name:
ForEach ($server in $servers)
{
Set-Service -computername $server -Name sacsvr -StartupType Disabled -PassThru |
Select #{Name = 'ServerName'; Expression = {$server}}, Name, DisplayName, Status
}
The above is shorthand for:
Select-Object -Property (properties)
The -Property parameter allows you to select any arbitrary grouping of properties on the type of object being piped in. Another parameter, -InputObject allows us to pipe in objects by value.
I've got a command that can list all app pools on a machine:
Get-WmiObject -namespace "root/MicrosoftIISv2" -class IIsApplicationPool |Select-Object -property #{N="Name";E={$name = $_.Name; $name.Split("/")[2] }} | Format-Table
I want to set the managedpipeline of every app pool on the box. I've tried this:
Get-WmiObject -namespace "root/MicrosoftIISv2" -class IIsApplicationPool |Select-Object -property #{N="Name";E={$name = $_.Name; $name.Split("/")[2] }} | ForEach-Object {cmd /c "c:\windows\system32\inetsvr\appcmd.exe set apppool $name /managedPipleineMode:"Classic"'}
This is giving me a "cannot find the path specified" error. Any ideas how I can this to work?
In order to set the Managed Pipeline mode (or any property of the AppPool), you need to use Set-ItemProperty. But it gets more fun than that:
Set-ItemProperty takes a Path as its input. Get-ChildItem will
return you a collection of ConfigurationElement objects, not Path
strings.
ManagedPipelineMode is internally stored as an integer, so
you have to know the correct "magic" number to pass in.
Fortunately, that is documented here, in the "Remarks" section.
This did the trick for me:
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools |
Select-Object -ExpandProperty PSPath |
ForEach-Object { Set-ItemProperty $_ ManagedPipelineMode 1 }
following the documentation :
$iisAppPoolName = "MyPool"
$appPool = New-WebAppPool -Name $iisAppPoolName
$appPool.managedPipelineMode = "Classic"
$appPool |Set-Item
I tested, IIS 8.0, Windows server 2012, and it works.
If you're on Powershell V2 I would use the WebAdministration module e.g. from an elevated prompt:
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | Foreach {$_.ManagedPipelineMode = 'Classic'}