Multiple column output using a hashtable - powershell

I am trying to create a Hash Table that contains 3 columns.
SERVER_NAME PROCESS_NAME SERVER_STATUS PROCESS_AVAILABLE
SERVER1 app1.exe RUNNING YES
SERVER1 app2.exe RUNNING NO
SERVER2 app1.exe OFFLINE NO
SERVER2 app2.exe OFFLINE NO
SERVER3 app1.exe RUNNING YES
SERVER3 app2.exe RUNNING YES
So far, I've tried this
$SERVERLIST = Get-Content "$PSScriptRoot\servers\serverManager.bin"
$PROCESSMONITOR = Get-Content "$PSScriptRoot\process\application.bin"
$testList = #{Name=$SERVERLIST;Process=$PROCESSMONITOR}
The list of servers are in the "serverManager.bin" file. This is a CSV file that contains a list of the servers.
The list of processes that I am interested in monitoring are in the "application.bin" file. This is a CSV file that contains a list of the applications (as seen by PowerShell). [see code below]
Get-Process -ComputerName $server -name $process -ErrorAction SilentlyContinue
I want to build a report which tells an admin which server is running and which process is running from the list that we are interested in monitoring.
I can check if the process is running
I can check if a server is online
My question is what do I need to do to get output like what's posted above

While hashtables play a part in this answer you are not looking for hashtables at all really. Looking at about_hash_tables
A hash table, also known as a dictionary or associative array, is a
compact data structure that stores one or more key/value pairs.
While you can nest whatever you want into the value you really are not looking for a hashtable. What I think you want is a custom PowerShell object that contains the results of each of your queries.
Get-Process does take arrays for both -Computer and -Name but they would omit results where either the computer does not exist or the process does not. Since you want that information you need to run a single cmdlet for each computer/process pair.
I use a hashtable only to create each individual "row" which is converted to a PowerShell object and collected as an array. I don't want to confuse but I know this working with at least 2.0 which is why I do it this way.
$SERVERLIST | ForEach-Object{
$computer = $_
$PROCESSMONITOR | ForEach-Object{
$process = $_
$props = #{
Server_Name = $computer
Process_Name = $process
}
# Check if the computer is alive. Better this was if $processes is large
If(Test-Connection $computer -Quiet -Count 1){
$props.Server_Status = "Running"
$result = Get-Process -Name $process -ComputerName $computer -ErrorAction SilentlyContinue
If($result){
$props.Process_Available = "Yes"
} else {
$props.Process_Available = "No"
}
} else {
$props.Server_Status = "Offline"
$props.Process_Available = "No"
}
New-Object -TypeName psobject -Property $props
}
} | Select Server_Name,Process_Name,Server_Status,Process_Available
So now that we have a proper object you can now use other cmdlets like Where-Object, Sort-Object and etc.

Related

Change a Windows product key remotely with PowerShell

I'm trying to install/activate a MAK key on remote servers. All of them have RemotePS enabled and firewall exception rules in place.
$Results = Invoke-Command -ComputerName Server1 {
$Props = #{ComputerName = $env:ComputerName}
slmgr.vbs /ipk "12345-12345-12345-12345-12345"
$LicStatus = slmgr.vbs /dlv
$Props.Add('LicenseStatus',$LicStatus)
New-Object -TypeName PSObject -Property $Props
}
$Results | Select-Object ComputerName,LicenseStatus
The above does install the MAK key but I don't get any confirmation of this process which is why I've tried adding in the license check option (/dlv) but get nothing returned in the LicenseStatus field. I'm assuming this is because it returns a multi-value maybe!?
Ultimately I'm just trying to get confirmation that the key was installed. There are articles out there about performing this using RemotePS but they all say a notification message is returned for each computer which isn't the case in my experience: https://4sysops.com/archives/change-a-product-key-remotely-with-powershell/
Any ideas how I can check this?
I would call the slmgr.vbs script using Cscript.exe in order to get the results as string array. Otherwise the system will default to using Wscript.exe which is designed to output everything in a messagebox.
Unfortunately, all output of slmgr is localized, so using a regex or something on the LicenseStatus is a no go (on a Dutch NL machine it reads 'Licentiestatus')
What you can do is using switch /dli, because that returns a string array where the last (not empty) value has the status.
Try
$Results = Invoke-Command -ComputerName Server1 {
# install MAK key
$null = cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /ipk "12345-12345-12345-12345-12345"
# test LicenseStatus
$LicStatus = (((cscript.exe "$env:SystemRoot\System32\slmgr.vbs" /dli) |
Where-Object { $_ -match '\S' })[-1] -split ':', 2)[1].Trim()
# return an object
[PsCustomObject]#{
ComputerName = $env:COMPUTERNAME
LicenseStatus = $LicStatus
}
}
$Results

Using Get-Job to test-connection, but quite different [duplicate]

This question already has answers here:
If using Test-Connection on multiple computers with -Quiet how do I know which result is for which computer?
(2 answers)
Closed 2 years ago.
It's my first post here, I'm tring to write scripts on PS on my own, now my target is to write script that checks if computer is online at network, for example: test-Connection 192.168.0.1, 2, 3 etc. Doing this one by one on loop for takes some time if some computers are offline, I've found some tutorials on this site to use -AsJob param, but I'm not really Sure how could it work. I mean I'd like to output every checked PC to excel, so i need if operator. eg:
if (Job1 completed successfull (computer pings)){
do smth}...
I need to get output from Job boolean (true/false), but one by one. I'm taking my first steps in PS, I've made program that checks it one by one in for loop, but as i said it take some time till my excel file fill...
I can see, that AsJob makes working more effective and I think it's important to understand it
Thanks and sorry for bad text formatting, by the time I'll go on with this!
In your example, in the Start-Job scriptblock you are trying to access $_ which is not available in the codeblock scope. If you replace $_ with $args[0] it should work since you are passing in the $ip value as an argument
Your Example
$ipki = Get-Content 'C:\Users\pchor\Desktop\ipki.txt'
foreach ($ip in $ipki) {
Start-Job -Name "$ip" -ScriptBlock {
Test-Connection $_ -Count 1 # <---- replace $_ with $args[0]
} -ArgumentList $_ # <----- change $_ to $ip
}
You'll probably also want to wait for all the jobs to finish. I recommend something like this
$computers = #(
'www.google.com'
'www.yahoo.com'
)
$jobs = $computers |
ForEach-Object {
Start-Job -ScriptBlock {
[pscustomobject]#{
Computer = $using:_
Alive = Test-Connection $using:_ -Count 1 -Quiet
}
}
}
# Loop until all jobs have stopped running
While ($jobs |
Where-Object { $_.state -eq 'Running' }) {
"# of jobs still running $( ($jobs | Where-Object {$_.state -eq 'Running'}).Count )";
Start-Sleep -Seconds 2
}
$results = $jobs | Receive-Job | Select-Object Computer, Alive
$results | Format-Table
Output
Computer Alive
-------- -----
www.google.com True
www.yahoo.com True
To modify the properties to what you want there are different ways of doing this. Easiest in this case is probably to use a calculated property
$newResults = $results |
Select-Object Computer,
#{Label = 'State'; Expression = { if ($_.Alive) { 'Online' } else { 'Offline' } } }
Objects will now look like this (I added another fake address to illustrate offline state)
Computer State
-------- -----
www.google.com Online
www.yahoo.com Online
xxx.NotAValidAddress.xxx Offline
You can then export the objects to csv using Export-csv
$newResults | Export-Csv -Path c:\temp\output.csv

Unable to show export-csv in PoweSshell

I have been researching the web to see what am I missing and can't find out, I run the command it goes thru the list of computers but the export doc is always empty.
Here is the code
foreach ($computer in Get-Content "\\NETWORK PATH\user-computers.txt") {
Write-host $computer
$colDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $computer
$Report = #()
# Set our filename based on the execution time
$filenamestring = "$computer-$(get-date -UFormat "%y-%b-%a-%H%M").csv"
foreach ($objDrive in $colDrives) {
# For each mapped drive – build a hash containing information
$hash = #{
ComputerName = $computer
MappedLocation = $objDrive.ProviderName
DriveLetter = $objDrive.DeviceId
}
# Add the hash to a new object
$objDriveInfo = new-object PSObject -Property $hash
# Store our new object within the report array
$Report += $objDriveInfo
}}
# Export our report array to CSV and store as our dynamic file name
$Report | Export-Csv -LiteralPath "\\NETWORK PATH\Drive-Maps.csv" -NoTypeInformation
I want to know what each computer currently got mapped network drives, thanks for all your help and guidance.
I'm not sure why you're not getting output. I've rewritten your script for a few reasons I'd like to point out. First, your variable naming is not very clear. I'm guessing you come from a VBScripting background. Next, you're creating an array and then adding to it - this is simply not needed. You can capture the output of any loop/scriptblock/etc directly by assigning like tihs.
$Report = foreach($thing in $manythings){Do lots of stuff and everything in stdout will be captured}
If you write your script in a way that takes advantage of the pipeline, you can do even more. Next, creating the object with New-Object is slow compared to using the [PSCustomObject] type accelerator introduced in V3. Finally, it seems you create a custom csv for each computer but in the end you just export everything to one file. I'm going to assume you are wanting to collect all this info and put in one CSV.
My recommendation for you to help troubleshoot, run this against your machines and confirm the output on the screen. Whatever you see on the screen should be captured in the report variable. (Except write-host, it's special and just goes to the console)
$computerList = "\\NETWORK PATH\user-computers.txt"
$reportFile = "\\NETWORK PATH\Drive-Maps.csv"
Get-Content $computerList | ForEach-Object {
Write-host $_
$mappedDrives = Get-WmiObject Win32_MappedLogicalDisk -ComputerName $_
foreach ($drive in $mappedDrives)
{
# For each mapped drive – build a hash containing information
[PSCustomObject]#{
ComputerName = $_
MappedLocation = $drive.ProviderName
DriveLetter = $drive.DeviceId
}
}
} -OutVariable Report
Once you know you have all the correct info, run this to export it.
$Report | Export-Csv -LiteralPath $reportFile -NoTypeInformation

Trouble executing powershell script on multiple remote machines

I need to generate a list of all users on our network who are members of their workstation's local administrators group. I found a script here https://gallery.technet.microsoft.com/scriptcenter/List-local-group-members-762b48c5#content which was written to list local group members by executing a WMI query through Powershell. I've tested this script and it works well, but I've been trying to modify it to take in a list of computers to check and that's where I've run into trouble. Here's what I've done:
function LocalAdmins
{
param([string]$GroupName = "Administrators")
begin
{
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Initialize an array to hold the results of the query
$arr = #()
# hash table for storing computer name, member pairings
$hash = #();
}
process
{
foreach ($machine in $WorkStations)
{
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
# Parse out the username from each result and append it to the array
if ($wmi -ne $null)
{
foreach($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
# Return a hash table comprised of two columns: Computer Name & Members
$hash += #{ComputerName=$machine;Members=$arr}
}
}
end
{
return $hash
}
}
When I ran the unmodified script here's what I got as output:
PS > (Get-LocalGroupMembers -ComputerName "<COMPUTER NAME>" -GroupName "Administrators").Members
ACCOUNTNAME
ACCOUNTNAME
ACCOUNTNAME
PS >
However, when I run the version of this script that I modified I get this:
PS > (LocalAdmins -GroupName "Administrators").Members
PS >
I'm fairly certain that the issue lies either in how I've setup the first foreach loop to run the wmi query or how the results of that query are being stored in the hash table. I'm not sure what I could do differently to fix the issue.
Thanks in advance to anyone who can help!
UPDATE
Per mortenya's suggestion, I edited my test text file to only include one computer in it. Doing so, along with taking out the foreach ($machine in $computers) loop worked as expected producing the following result:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- ----
ComputerName {computerName.domain}
Members {account, account, account, account}
>>
However, going back and trying to get this to work when incorporating multiple machines using the code above (I've updated it since my initial post), I get the following:
>> LocalAdmins -GroupName "Administrators"
Name Value
---- -----
ComputerName computerName1.domain
Members {}
ComputerName computerName2.domain
Members {}
>>
Why is it that with one machine in the list I can get the members of the Administrator group, but adding a second computer to the list makes it so I can not retrieve members from that group on either machine?
So, if you're going to use Begin{}, Process{}, and End{}, use them for what they're meant for, in the Begin{} block, initialize all your arrays and constant varaibles.
Begin {
# Get all workstations listed in this text file
$WorkStations = Get-Content -Path C:\useful_lists\testLocal.txt
# Store the contents of that list in an array
$computers = #()
$hash = #()
}
Outside of that, I did this same thing a few months ago, it's a little messy, but it spit out a list of computers and who was in the Local Administrators group. It was partially to practice some different methods.
$output = 'c:\psresults\ListOfLocalAdministratorsGroup.txt'
$results = New-Object System.Collections.ArrayList
$objSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
$objgroup = $objSID.Translate( [System.Security.Principal.NTAccount])
$objgroupname = ($objgroup.Value).Split("\")[1]
foreach($server in (Get-ADComputer -Filter *).name)
{
$admins = New-Object System.Collections.ArrayList
$group =[ADSI]"WinNT://$server/$objgroupname"
$members = #($group.psbase.Invoke("Members"))
$members | foreach {
$obj = new-object psobject -Property #{
Server = $Server
Admin = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)
}
#$obj
$admins.Add($obj)
}
$results.Add($admins)
}
$results | Out-File $Output
I found the meat of that somewhere and then modified it a bit.
EDIT: I just put this into ISE and it seems to work fine
$machine = "testsrv"
$groupname = "Administrators"
$wmi = Get-WmiObject -ComputerName $machine -Query `
"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$machine',Name='$GroupName'`""
if ($wmi -ne $null)
{
foreach ($item in $wmi)
{
$arr += ($item.PartComponent.Substring($item.PartComponent.IndexOf(',') + 1).Replace('Name=', '').Replace("`"", ''))
}
}
$hash = #{ComputerName=$machine;Members=$arr}
return $hash
Get it working on one machine, then start trying to add the loops back in.
EDIT 2.0:
I made a .txt file with only computer names in it, not the FQDN, that works fine for me. I can run it and get results using your script with minor modification.
Despite what I'd said about the Begin{} block, the $arr variable will need to be initialized inside the foreach ($machine in $WorkStations) loop. The reason for this is that when the loop runs, it will create the $arr array, add the data we want, insert that data into a global variable, and then clean up the $arr variable. If we make this global, it won't be cleaned up until the function is done, and we will just keep adding to it, which isn't what we actually want in this case.
The problem you're having with getting multiple machines to work is likely how you're building your results table.

How to verify whether a windows server has mountpoint or not using WMI

I am generating a report where I need to find which servers has mountpoints configured on it..
can you help how to get that infor using WMI or powershell.
I mean I need to identify the servers, if mountpoints exists in it.. and also their names....
Get a list of all servers from textfile, AD, etc. and run a foreach loop with something like this:
Get-Wmiobject -query “select name,driveletter,freespace from win32_volume where drivetype=3 AND driveletter=NULL” -computer servername
A quick google search for "windows mount point wmi" would return THIS (source).
Then export the results to CSV, HTML or whatever you need. Your question is lacking a lot of details and any sign of effort from your part, so I can't/won't go any further.
UPDATE: Does this help? It lists mount points(folder paths, not driveletters).
$servers = #("server1","server2","server3","server4","server5")
$servers | % {
$mountpoints = #(Get-WmiObject Win32_MountPoint -ComputerName $_ | Select-Object -ExpandProperty Directory | ? { $_ -match 'Win32_Directory.Name="(\w:\\\\.+)"' }) | % { [regex]::Match($_,'Win32_Directory.Name="(\w:\\\\.+)"').Groups[1].Value -replace '\\\\', '\' }
if($mountpoints.Count -gt 0) {
New-Object psobject -Property #{
Server = $_
MountPoints = $mountpoints
}
}
}
Server MountPoints
------ -----------
{server1} {D:\SSD, C:\Test}