I created a script to collect remote SQL servers
#### Get number of SQL servers
$sql_servers = #()
foreach ($server in $servers){
# Loop through each server and check if server has service "MSSQLSERVER"
Try{
$sql = get-service -computername $server.DNSHOstname -ErrorAction Stop | where {$_.Name -eq "MSSQLSERVER"} | select MachineName
$sql_servers += New-Object PSObject -Property #{
Machine = $sql.MachineName
}
}
catch [Exception]
{
if ($_.Exception.GetType().Name -like "*COMException*") {
Write-Verbose -Message ('{0} is unreachable' -f $server.DNSHOstname) -Verbose
}
else{
Write-Warning $Error[0]
}
}
}
I'm getting desired results but variable contains multiple empty lines:
$sql_servers
Machine
-------
SQL1
SQL2
SQL3
I tried following to remove those blank lines without success.
$sql_servers = $sql_servers | Where-Object {$_}
$sql_servers = $sql_servers | ? {$_ -ne ""}
How to remove empty (blank) lines from variable ?
EDIT:
I found a workaround by removing hashtable property, instead of
$sql_servers += New-Object PSObject -Property #{
Machine = $sql.MachineName
}
}
i just set $sql_servers += $sql and no empty lines, but i'm curious is it possible to remove empty line using hash table.
Thanks
First thing to point out is your variable $sql_servers does not contain hashtables, but rather PSCustomObjects with one property. For this specific scenario you could remove empty entries by adjusting your command to
$sql_servers = $sql_servers | Where-Object {$_.machinename}
If it were hashtables, you could use
$sql_servers = $sql_servers | Where-Object {$_.values}
Here is a simple demonstration of both.
PSObject
1..5 | % {
if($_ % 2 -eq 0)
{
$num = $_
}
else
{
$num = $null
}
[PSCustomObject]#{
MachineName = $num
}
} -ov sql_servers
MachineName
-----------
2
4
$sql_servers | ? {$_.machinename} -ov sql_servers
MachineName
-----------
2
4
Hashtable
1..5 | % {
if($_ % 2 -eq 0)
{
$num = $_
}
else
{
$num = $null
}
#{
MachineName = $num
}
} -ov sql_servers
Name Value
---- -----
MachineName
MachineName 2
MachineName
MachineName 4
MachineName
$sql_servers | ? {$_.values} -ov sql_servers
Name Value
---- -----
MachineName 2
MachineName 4
Related
I want to work with a CSV file of more than 300,000 lines. I need to verify information line by line and then display it in a .txt file in the form of a table to see which file was missing for all servers. For example
Name,Server
File1,Server1
File2,Server1
File3,Server1
File1,Server2
File2,Server2
...
File345,Server76
File346,Server32
I want to display in table form this result which corresponds to the example above:
Name Server1 Server2 ... Server 32 ....Server 76
File1 X X
File2 X X
File3 X
...
File345 X
File346 X
To do this actually, I have a function that creates objects where the members are the Server Name (The number of members object can change) and I use stream reader to split data (I have more than 2 columns in my csv so 0 is for the Server name and 5 for the file name)
$stream = [System.IO.StreamReader]::new($File)
$stream.ReadLine() | Out-Null
while ((-not $stream.EndOfStream)) {
$line = $stream.ReadLine()
$strTempo = $null
$strTempo = $line -split ","
$index = $listOfFile.Name.IndexOf($strTempo[5])
if ($index -ne -1) {
$property = $strTempo[0].Replace("-", "_")
$listOfFile[$index].$property = "X"
}
else {
$obj = CreateEmptyObject ($listOfConfiguration)
$obj.Name = $strTempo[5]
$listOfFile.Add($obj) | Out-Null
}
}
When I export this I have a pretty good result. But the script take so much time (between 20min to 1hour)
I didn't know how optimize actually the script. I'm beginner to PowerShell.
Thanks for the futures tips
You might use HashSets for this:
$Servers = [System.Collections.Generic.HashSet[String]]::New()
$Files = #{}
Import-Csv -Path $Path |ForEach-Object {
$Null = $Servers.Add($_.Server)
if ($Files.Contains($_.Name)) { $Null = $Files[$_.Name].Add($_.Server) }
else { $Files[$_.Name] = [System.Collections.Generic.HashSet[String]]$_.Server }
}
$Table = foreach($Name in $Files.get_Keys()) {
$Properties = [Ordered]#{ Name = $Name }
ForEach ($Server in $Servers) {
$Properties[$Server] = if ($Files[$Name].Contains($Server)) { 'X' }
}
[PSCustomObject]$Properties
}
$Table |Format-Table -Property #{ expression='*' }
Note that in contrast to PowerShell's usual behavior, the .Net HashSet class is case-sensitive by default. To create an case-insensitive HashSet use the following constructor:
[System.Collections.Generic.HashSet[String]]::New([StringComparer]::OrdinalIgnoreCase)
See if this works faster. Change filename as required
$Path = "C:\temp\test1.txt"
$table = Import-Csv -Path $Path
$columnNames = $table | Select-Object -Property Server -Unique| foreach{$_.Server} | Sort-Object
Write-Host "names = " $columnNames
$groups = $table | Group-Object {$_.Name}
$outputTable = [System.Collections.ArrayList]#()
foreach($group in $groups)
{
Write-Host "Group = " $group.Name
$newRow = New-Object -TypeName psobject
$newRow | Add-Member -NotePropertyName Name -NotePropertyValue $group.Name
$servers = $group.Group | Select-Object -Property Server | foreach{$_.Server}
Write-Host "servers = " $servers
foreach($item in $columnNames)
{
if($servers.Contains($item))
{
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue 'X'
}
else
{
#if you comment out next line code doesn't work
$newRow | Add-Member -NotePropertyName $item -NotePropertyValue ''
}
}
$outputTable.Add($newRow) | Out-Null
}
$outputTable | Format-Table
Help me with some problem. I am new in PS and
i need output data to table view.
Looks like this.
name1 name2
----- -----
value1 value2
But i have:
$a=(get-service).name
$b=(get-service).Status
foreach ($name in $a)
{
$data = [pscustomobject]#{name1 = $name}
}
$data
RESULT
name1
-----
XboxNetApiSvc
WITHOUT FOREATCH
$a=(get-service).name
$b=(get-service).Status
$data = [pscustomobject]#{name1 = $a ; name2 = $b }
$data
Result
name1 name2
----- -----
{2GISUpdateService, AcronisActiveProtectionService, AcrSch2Svc, AdobeARMservice...} {Stopped, Running, Running, Running...}
All of that i need for this script
$list = ((Get-ADComputer -SearchBase "OU=PC,DC=cbu,DC=lan" -Filter {Enabled -eq "True" } -Properties DNSHostName).DNSHostName)
$encoding = [System.Text.Encoding]::UTF8
$up = #()
$down = #()
$table= #()
foreach ($pc in $list)
{
if ((Test-Connection -Count 1 -computer $pc -quiet) -eq $True)
{
$up += $pc
#write-host $pc "is up"
}
else
{
$down += $pc
#Write-Host $pc "down"
}
}
After all i need output values of $up and $down in 2 columns
You probably have a custom commandlet but you can run something similar to:
(Get-Service) | select Name,Status | Format-Table
UPDATE
After reading your update. At the end of your script you have two arrays $up and $down. I will declare it the static way to make an example easier
$up = #('pc1', 'pc2')
$down = #('pc3','pc4', 'pc5')
Because arrays can be diffrent length you need to calculate maximum length with:
$max = ($up, $down | Measure-Object -Maximum -Property Count).Maximum
And than create an object which "merges" above arrays with:
0..$max | Select-Object #{n="Up";e={$up[$_]}}, #{n="Down";e={$down[$_]}}
The output is:
Up Down
-- ----
pc1 pc3
pc2 pc4
pc5
If you are using Get-Service as example only then, you can just use Select :
Get-Service | Select Name, Status
Else
$MyList | Select Name1, Name2
Moreover, if you have a complex command and you want to extract a table of PSObject :
Get-Process | Select-Object -Property ProcessName, Id, WS
Read more about Select-Object : https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-object?view=powershell-4.0
Yes Piotr Stapp is rights. You can use Format-Table
Example 1:
Get-Process | Sort-Object starttime | Format-Table -View starttime
Example 2:
(Get-Service) | select Name,Status | Format-Table
Example 3:
Get-Process | FT
I am trying to convert a 80K row data csv with 30 columns to sorted and filtered CSV based on specific column data from orignal CSV.
For Example My Data is in below format:
PatchName MachineName IPAddress DefaultIPGateway Domain Name USERID UNKNOWN NOTAPPLICABLE INSTALLED APPLICABLE REBOOTREQUIRED FAILED
KB456982 XXX1002 xx.yy.65.148 xx.yy.64.1 XYZ.NET XYZ\ayzuser YES
KB589631 XXX1003 xx.yy.65.176 xx.yy.64.1 XYZ.NET XYZ\cdfuser YES
KB456982 ABC1004 xx.zz.83.56 xx.zz.83.1 XYZ.NET XYZ\mnguser YES
KB456982 8797XCV xx.yy.143.187 xx.yy.143.184 XYZ.NET WPX\abcuser YES
Here MachineName would be filtered to Uniq and PatchName would transpose to Last Columns headers with holding "UNKNOWN, NOAPPLICABLE, INSTALLED, FAILED, REBOOTREQUIRED columns Values if YES occurred -
Expected Result:
MachineName IPAddress DefaultIPGateway Domain Name USERID KB456982 KB589631
XXX1002 xx.yy.65.148 xx.yy.64.1 XYZ.NET XYZ\ayzuser UNKNOWN
XXX1003 xx.yy.65.176 xx.yy.64.1 XYZ.NET XYZ\cdfuser NOTAPPLICATBLE
ABC1004 xx.zz.83.56 xx.zz.83.1 XYZ.NET XYZ\mnguser UNKNOWN
8797XCV xx.yy.143.187 xx.yy.143.184 XYZ.NET WPX\abcuser FAILED
Looking for help to achieve this, so far I am able to transpose PathcName rows to columns but not able to include all the columns along with and apply the condition. [It takes 40 Minutes to process this]
$b = #()
foreach ($Property in $a.MachineName | Select -Unique) {
$Props = [ordered]#{ MachineName = $Property }
foreach ($Server in $a.PatchName | Select -Unique){
$Value = ($a.where({ $_.PatchName -eq $Server -and $_.MachineName -eq $Property })).NOTAPPLICABALE
$Props += #{ $Server = $Value }
}
$b += New-Object -TypeName PSObject -Property $Props
}
This is what I came up with:
$data = Import-Csv -LiteralPath 'C:\path\to\data.csv'
$lookup = #{}
$allPatches = $data.PatchName | Select-Object -Unique
# Make 1 lookup entry for each computer, to keep the username and IP and so on.
# Add the patch details from the current row (might add more than one patch per computer)
foreach ($row in $data)
{
if (-not $lookup.ContainsKey($row.MachineName))
{
$lookup[$row.MachineName] = ($row | Select-Object -Property MachineName, IPAddress, DefaultIPGateway, DomainName, UserID)
}
$patchStatus = $row.psobject.properties |
Where-Object {
$_.name -in #('applicable', 'notapplicable', 'installed', 'rebootrequired', 'failed', 'unknown') -and
-not [string]::IsNullOrWhiteSpace($_.value)
} |
Select-Object -ExpandProperty Name
$lookup[$row.MachineName] | Add-Member -NotePropertyName $row.PatchName -NotePropertyValue $patchStatus
}
# Pull the computer details out of the lookup, and add all the remaining patches
# so they will convert to CSV properly, then export to CSV
$lookup.Values | ForEach-Object {
$computer = $_
foreach ($patch in $allPatches | where-object {$_ -notin $computer.psobject.properties.name})
{
$computer | Add-Member -NotePropertyName $patch -NotePropertyValue ''
}
$computer
} | Export-Csv -LiteralPath 'c:\path\to\output.csv' -NoTypeInformation
I have adapted an existing Powershell script to query a list of servers for logged in (or disconnected) user sessions using quser /server:{servername} and output the results into a CSV file. It does output any logged in users but what it doesn't capture is servers that had 0 users or weren't accessible (server offline, rpc not available, etc.). I'm assuming that is because these other conditions are "errors" rather than command output.
So if it hits a server with no users it outputs "No User exists for *" in the console running the script. If it hits a server that it can't reach it outputs "Error 0x000006BA enumerating sessionnames" and on a second line "Error [1722]:The RPC server is unavailable." in the console running the script. So neither of these conditions show in the output.csv file.
I wanted to know if someone could suggest how I could also capture these conditions in the CSV as "$Computer has no users" and "$Computer Unreachable"
Here is the script
$ServerList = Read-Host 'Path to Server List?'
$ComputerName = Get-Content -Path $ServerList
foreach ($Computer in $ComputerName) {
quser /server:$Computer | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = #{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
New-Object -TypeName PSCustomObject -Property $HashProps |
Select-Object -Property ServerName,UserName,State,LogonTime |
ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Out-File -Append .\output.csv
}
}
Anyone curious why I'm using ConvertTo-CSV rather than Export-CSV which would be cleaner, is because the servers I'm running this from are running Powershell 2.0 in which Export-CSV doesn't support -Append. I'm not concerned as the output works for what I need, but if someone has a better suggestion for this feel free to comment.
So we have some updates to the script. If there is any error using quser we capture that as a special entry where the server name will read "Error contacting $computer" and other text that will give context to the error..
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = #{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
IdleTime = ""
LogonTime = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
} Else {
$results | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = #{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} | Select-Object -Property ServerName,UserName,State,LogonTime |
Export-Csv -NoTypeInformation .\output.csv
Part of the issue is that since this is not a PowerShell cmdlet capturing stderr in order to parse it needs to work differnetly. Playing with $erroractionpreference is an option as well but this is a first draft. We use 2>&1 to capture the error into $results to hide the message from the screen. Then we use an If to see if the last command succeeded.
In the event of an error
I used a switch statement with the error text. So you can tailor the output based on the the text returned in $results
Some minor changes
Most of the rest of your code is the same. I moved the object creation outside the If statement so that errors could be logged and changed to a Export-CSV as PowerShell will work out the details of that for you.
Unless you intend to have multiple passes of this function over time that you want to capture into the same file.
Console Output before export
ServerName UserName State LogonTime
---------- -------- ----- ---------
serverthing01 bjoe Active 4/9/2015 5:42 PM
Error contacting c4093 RPC server is unavailable [1722]
Error contacting c4094 Access is denied [5]
You can see there is output for each server even though the last two had separate reasons for not having proper output. If you ever see "Something else" the there was an error that did not have a specific message attached to the error.
When that happens look under State and the error number is displayed. Then you just need to update the Switch accordingly.
Significant Update
I was not sure what was the issue where is was dropping the extra lines for multiple users but I already have a dynamic parsing code for positionally delimited text so I am bringing that in here.
Function ConvertFrom-PositionalText{
param(
[Parameter(Mandatory=$true)]
[string[]]$data
)
$headerString = $data[0]
$headerElements = $headerString -split "\s+" | Where-Object{$_}
$headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
$data | Select-Object -Skip 1 | ForEach-Object{
$props = #{}
$line = $_
For($indexStep = 0; $indexStep -le $headerIndexes.Count - 1; $indexStep++){
$value = $null # Assume a null value
$valueLength = $headerIndexes[$indexStep + 1] - $headerIndexes[$indexStep]
$valueStart = $headerIndexes[$indexStep]
If(($valueLength -gt 0) -and (($valueStart + $valueLength) -lt $line.Length)){
$value = ($line.Substring($valueStart,$valueLength)).Trim()
} ElseIf ($valueStart -lt $line.Length){
$value = ($line.Substring($valueStart)).Trim()
}
$props.($headerElements[$indexStep]) = $value
}
New-Object -TypeName PSCustomObject -Property $props
}
}
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$HashProps = #{}
$exportedprops = "ServerName","UserName","State",#{Label="LogonTime";Expression={$_.Logon}}
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = #{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
Idle = ""
Time = ""
Logon = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} Else {
ConvertFrom-PositionalText -data $results | Add-Member -MemberType NoteProperty -Name "ServerName" -Value $computer -PassThru
}
} | Select-Object -Property $exportedprops |
Export-Csv -NoTypeInformation .\output.csv
Biggest difference here is we use ConvertFrom-PositionalText to parse the details from quser. Needed to zero out $HashProps = #{} which was causing conflicting results across mutliple runs. For good measure got the output of the function and the dummy error data to have the same parameter sets. Used $exportedprops which has a calculated expression so that you could have the headers you wanted.
New Output
ServerName USERNAME STATE LogonTime
---------- -------- ----- ---------
server01 user1 Disc 3/12/2015 9:38 AM
server01 user2 Active 4/9/2015 5:42 PM
Error contacting 12345 Access is denied [5]
Error contacting 12345 RPC server is unavailable [1722]
svrThg1 user3 Active 4/9/2015 5:28 PM
svrThg1 user4 Active 4/9/2015 5:58 PM
svrThg1 user-1 Active 4/9/2015 9:50 PM
svrThg1 bjoe Active 4/9/2015 10:01 PM
I am trying to compare the values of two variables but the contents of those two strings are in different orders
Example:
$Var1 = "item1"
$Var1 += "item2"
$Var2 = "item2"
$Var2 = "item1"
How can I compare those two variables to see if they both are equal?
===== UPDATED WITH EXAMPLE =====
EXAMPLE: Get objects and sort them.
$Computers = (Get-Content "$PWD\Computers.txt").GetEnumerator() | Sort-Object {"$_"}
EXAMPLE: Add the results and sort them.
$Successful += $Computer
$Successful = $Successful.GetEnumerator() | Sort-Object {"$_"}
EXAMPLE SCRIPT: Used the examples above to create the following script. The example allowed me to check the results, instead of count, but by content allowing me to get more accurate comparison. Before I was using "Successful.count -eq Computers.count" which wouldn't check if a computer was inputted twice.
$Computers = (Get-Content "$PWD\Computers.txt").GetEnumerator() | Sort-Object {"$_"}
$HotFixes = Get-Content "$PWD\HotFixes.csv"
CLS
While (!$Successful -OR $Successful -ne $Computers) {
foreach ($Computer in $Computers) {
$MissingCount = 0
IF (!$Successful -NotLike "*$Computer*") {
Write-Host "$Computer`: Connecting"
If (Test-Connection -ComputerName $Computer -Count 1 -quiet) {
Write-Host "$Computer`: Connected"
[string]$Comparison = get-hotfix -ComputerName $Computer | Select -expand HotFixID
ForEach ($HotFix in $HotFixes) {
IF ($Comparison -NotLike "*$HotFix*") {
$Results += "$Computer,$HotFix"
$MissingCount++
}
}
Write-Host "$Computer`: $MissingCount Patches Needed"
$Successful += $Computer
$Successful = $Successful.GetEnumerator() | Sort-Object {"$_"}
} ELSE {
Write-Host "$Computer`: Unable to connect"
}
} ELSE {
Write-Host "$Computer already completed"
}
Write-Host "$Computer`: Complete"
Write-Host
}
}
$Results
If you want to find if the content is equal, regardless of characters position, you could break the string to its characters, sort the result and then use the Compare-Object cmdlet. No result means the variables are equal:
$v1 = $Var1.GetEnumerator() | Sort-Object {"$_"}
$v2 = $Var2.GetEnumerator() | Sort-Object {"$_"}
compare $v1 $v2