Foreach loop in powershell with hasharray - powershell

I'm writing a powershell script to ping all the servers and check which are offline. but i have a bug. By name it works perfectly. But when i do test-connection with an IP it seems to work BUT i cant output the name of the IP in the hashlist. Could someone help me figure this out? Thanks!!
System.Collections.Hashtable.keys Is online/available, This is what it outputs. But i want it to say "Servername is online/available"
#Creating IP Array list
$ip_array = #{
Server = [ipaddress] "192.168.1.1"
sws = [ipaddress] "192.168.1.1"
}
Foreach ($ip in $ip_array)
{
if((Test-Connection -IPAddress $ip.values.ipaddresstostring -quiet -count 1 ) -eq $false)
{
write-output("$ip.keys Is offline/unavailable, please troubleshoot connection, script is terminating") | Red
}
else
{
$ping = $true
write-output("$ip.keys Is online/available") | Green
}
}

PowerShell's default pipeline semantics (any collection that can be enumerated and unraveled will be) makes dictionaries a pain to work with - piping them anywhere would result in a list of disjoint key-value-pairs, dictionary itself lost.
For this reason, PowerShell refuses to automatically enumerate dictionaries, and you must manually obtain an enumerator in order to loop over the entries in it:
foreach($entry in $ip_hash.GetEnumerator()){
# reference `$entry.Key` or `$entry.Name` for the key (eg. Server)
# reference `$entry.Value` for the value (eg. 192.168.1.1)
}

If you really intend to use a Hashtable for this, combining IP addresses with computernames, change to something like this:
# creating IP Hashtable
$ip_hash = #{
'192.168.1.1' = 'Server1'
'192.168.1.2' = 'Server2'
# etcetera
}
# loop through the hash, key-by-key
foreach ($ip in $ip_hash.Keys) {
$ping = Test-Connection -ComputerName $ip -Quiet -Count 1 -ErrorAction SilentlyContinue
if(!$ping) {
Write-Host "Server $($ip_hash[$ip]) is offline/unavailable, please troubleshoot connection, script is terminating" -ForegroundColor Red
}
else {
Write-Host "Server $($ip_hash[$ip]) is online/available" -ForegroundColor Green
}
}
Output would look like:
The Keys in the hash must all have unique values

Related

Script to search several servers for connected client IP addresses

I'm trying to make a Powershell script to search 5 servers for connected client's IP addresses. There are 5 servers and clients are connected via a user tunnel and an asset one. I'm trying to make a looping script that asks for the asset number and username then searches all 5 servers then reports back the tunnel IPs.
My Powershell skills are very rudimentary. I've managed to make a script that mostly works, the trouble I seem to be having is getting the script to report negative results properly. Here's where I am so far:
Clear-Host
$continue = $true
while ($continue){
Write-Host "Tunnel IP finder" -ForegroundColor White
$Asset = Read-Host "Enter asset number"
$AssetAddress = "$asset.corporate.domain.com"
$User = Read-Host "Enter Username"
$Username = "$User#domain.com"
$servers = "RRAS_01","RRAS_02","RRAS_03","RRAS_04","RRAS_05"
Write-Host ""
$data1 = Foreach ($Server1 in $Servers)
{
Get-RemoteAccessConnectionStatistics -computername $Server1 | Where {$_.UserName -eq $AssetAddress} | Select ClientIPAddress | findstr /r "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"
}
foreach($item1 in $data1){
if($item1.length -gt 1){
Write-Host "Asset tunnel IP is $item1"-ForegroundColor Green}
}
if($item1.length -LT 1){
Write-Host "Unable to locate asset on RRAS servers"-ForegroundColor yellow
}
$data2 = Foreach ($Server2 in $Servers)
{
Get-RemoteAccessConnectionStatistics -computername $Server2 | Where {$_.UserName -eq $Username} | Select ClientIPAddress | findstr /r "[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"
}
foreach($item2 in $data2){
if($item2.length -gt 1){
Write-Host "User tunnel IP is $item2"-ForegroundColor Green}
}
if($item2.length -lt 1){
Write-Host "Unable to locate user on RRAS servers"-ForegroundColor yellow
}
Write-Host ""
}
When I search for an asset number and username of someone who is connected it is reporting the results back like this:
Tunnel IP finder
Enter asset number: N02312
Enter Username: SmithJ
Asset tunnel IP is 10.120.xxx.xxx
User tunnel IP is 10.120.xxx.xxx
AOVPN Tunnel IP finder
Enter asset number:
Which is what I was looking to achieve, it displays the IPs and loops to enter more if needed, however when I input details that is not currently connected I get:
Tunnel IP finder
Enter asset number:
Enter Username:
AOVPN Tunnel IP finder
Enter asset number:
It's not reporting the negative results. When I take the scripting off and just have it dump what it thinks $item1/2 is supposed to be it prints nothing, so as far as I can tell $item1/2.length -LT 1 should be doing it.
I've tried experimenting with Else and Elseif, but I can't seem to make those work. There are probably better ways of doing this, but my Powershell is still very basic.
Any help would be greatly appreciated.
Thanks in advance.
It seems if you loop through a collection that has $null value the $item.length will be equal to 1, not to 0 and its value will not be equal to $null. Maybe that´s what happening to you.
PS C:\Windows\System32\WindowsPowerShell\v1.0> $x = $null
PS C:\Windows\System32\WindowsPowerShell\v1.0> $x.length
0
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr = {$null}
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr.length
1
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr | % {$_.length}
1
PS C:\Windows\System32\WindowsPowerShell\v1.0> $arr | % {$_ -eq $null}
False
In your code you only check for the length to be either less or greater than 1 so in the case of the length being equal to 1 it would not print anything. Since the $null check doesnt work I think the best option would be to change the second comparison operator to less or equal instead of less than:
if($item1.length -LE 1){
Write-Host "Unable to locate asset on RRAS servers"-ForegroundColor yellow
}

Powersell - Remotely Query if User Exists Across Domain [Fastest]

Abstract
So I work for a company that has roughly 10k computer assets on my domain. My issue is the time it takes to query if a user exists on a computer to see if they've ever logged into said computer. We need this functionality for audits in case they've done something they shouldn't have.
I have two methods in mind I've researched to complete this task, and a third alternative solution I have not thought of;
-Method A: Querying every computer for the "C:\Users<USER>" to see if LocalPath exists
-Method B: Checking every computer registry for the "HKU:<SID>" to see if the SID exists
-Method C: You are all smarter than me and have a better way? XD
Method A Function
$AllCompFound = #()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
ForEach($Computer in $AllADComputers) {
$CName = $Computer.Name
if (Get-CimInstance -ComputerName "$CName" -ClassName Win32_Profile | ? {"C:\Users\'$EDIPI'" -contains $_.LocalPath}) {
$AllCompFound += $CName
} else {
#DOOTHERSTUFF
}
}
NOTE: I have another function that prompts me to enter a username to check for. Where I work they are numbers so case sensitivity is not an issue. My issue with this function is I believe it is the 'if' statement returns true every time because it ran rather than because it matched the username.
Method B Function
$AllCompFound = #()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
$hive = [Microsoft:Win32.RegistryHive]::Users
ForEach($Computer in $AllADComputers) {
try {
$base = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive, $Computer.Name)
$key = &base.OpenSubKey($strSID)
if ($!key) {
#DOSTUFF
} else {
$AllCompFound += $Computer.Name
#DOOTHERSTUFF
}
} catch {
#IDONTTHROWBECAUSEIWANTITTOCONTINUE
} finally {
if($key) {
$key.Close()
}
if ($base) {
$base.Close()
}
}
}
NOTE: I have another function that converts the username into a SID prior to this function. It works.
Where my eyes start to glaze over is using Invoke-Command and actually return a value back, and whether or not to run all of these queries as their own PS-Session or not. My Method A returns false positives and my Method B seems to hang up on some computers.
Neither of these methods are really fast enough to get through 10k results, I've been using smaller pools of computers in order to get test these results when requested. I'm by no means an expert, but I think I have a good understanding, so any help is appreciated!
First, use WMI Win32_UserProfile, not C:\Users or registry.
Second, use reports from pc to some database, not from server to pc. This is much better usually.
About GPO: If you get access, you can Add\Remove scheduled task for such reports through GPP (not GPO) from time to time.
Third: Use PoshRSJob to make parallel queries.
Get-WmiObject -Class 'Win32_USerProfile' |
Select #(
'SID',
#{
Name = 'LastUseTime';
Expression = {$_.ConvertToDateTime($_.LastUseTime)}}
#{
Name = 'NTAccount';
Expression = { [System.Security.Principal.SecurityIdentifier]::new($_.SID).Translate([System.Security.Principal.NTAccount])}}
)
Be careful with translating to NTAccount: if SID does not translates, it will cause error, so, maybe, it's better not to collect NTAccount from user space.
If you have no other variants, parallel jobs using PoshRSJob
Example for paralleling ( maybe there are some typos )
$ToDo = [System.Collections.Concurrent.ConcurrentQueue[string]]::new() # This is Queue (list) of computers that SHOULD be processed
<# Some loop through your computers #>
<#...#> $ToDo.Enqueue($computerName)
<#LoopEnd#>
$result = [System.Collections.Concurrent.ConcurrentBag[Object]]::new() # This is Bag (list) of processing results
# This function has ComputerName on input, and outputs some single value (object) as a result of processing this computer
Function Get-MySpecialComputerStats
{
Param(
[String]$ComputerName
)
<#Some magic#>
# Here we make KSCustomObject form Hashtable. This is result object
return [PSCustomObject]#{
ComputerName = $ComputerName;
Result = 'OK'
SomeAdditionalInfo1 = 'whateverYouWant'
SomeAdditionalInfo2 = 42 # Because 42
}
}
# This is script that runs on background. It can not output anything.
# It takes 2 args: 1st is Input queue, 2nd is output queue
$JobScript = [scriptblock]{
$inQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]$args[0]
$outBag = [System.Collections.Concurrent.ConcurrentBag[Object]]$args[1]
$compName = $null
# Logging inside, if you need it
$log = [System.Text.StringBuilder]::new()
# we work until inQueue is empty ( then TryDequeue will return false )
while($inQueue.TryDequeue([ref] $compName) -eq $true)
{
$r= $null
try
{
$r = Get-MySpecialComputerStats -ComputerName $compName -EA Stop
[void]$log.AppendLine("[_]: $($compName) : OK!")
[void]$outBag.Add($r) # We append result to outBag
}
catch
{
[void]$log.AppendLine("[E]: $($compName) : $($_.Exception.Message)")
}
}
# we return log.
return $log.ToString()
}
# Some progress counters
$i_max = $ToDo.Count
$i_cur = $i_max
# We start 20 jobs. Dont forget to say about our functions be available inside job
$jobs = #(1..20) <# Run 20 threads #> | % { Start-RSJob -ScriptBlock $JobScript -ArgumentList #($ToDo, $result) -FunctionsToImport 'Get-MySpecialComputerStats' }
# And once per 3 seconds we check, how much entries left in Queue ($todo)
while ($i_cur -gt 0)
{
Write-Progress -Activity 'Working' -Status "$($i_cur) left of $($i_max) computers" -PercentComplete (100 - ($i_cur / $i_max * 100))
Start-Sleep -Seconds 3
$i_cur = $ToDo.Count
}
# When there is zero, we shall wait for jobs to complete last items and return logs, and we collect logs
$logs = $jobs | % { Wait-RSJob -Job $_ } | % { Receive-RSJob -Job $_ }
# Logs is LOGS, not result
# Result is in the result variable.
$result | Export-Clixml -Path 'P:/ath/to/file.clixml' # Exporting result to CliXML file, or whatever you want
Please be careful: there is no output inside $JobScript done, so it must be perfectly done, and function Get-MySpecialComputerStats must be tested on unusual ways to return value that can be interpreted.

Getting quick Server Ping Results in Powershell

I have written below PowerShell script to get the ping results of server and it also generates the log for the same. My script works fine, also generates log but it take so much time as there are many servers. Is there any way I can quickly get the results. I have tried start-job but that generates error.
$logpath = $logpath +"\" +$Logname
Function Log($txt)
{
if (! (Test-Path $logpath)) {
New-Item $logpath |Out-Null
}
$date = Get-Date -Format "dd_MMM_yyyy_hh:mm:ss - "
$txt1 = ($date + $txt)
Add-Content "$logpath" "$txt1"
Add-Content "$logpath" " "
}
$ServerDetails=import-csv $Servercsv
foreach($servertest in $ServerDetails)
{
if((Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
if(!(Test-Connection -ComputerName $servertest.servers -Count 2))
{
Log("'" + $servertest.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
Assumed your "log" function works as expected you could make the rest of your code a little more efficient:
$ServerList = import-csv $Servercsv
foreach ($ComputerName in $ServerList) {
$Connection = Test-Connection -ComputerName $ComputerName.servers -Count 1 -Quiet
if ($Connection) {
Log("'" + $ComputerName.servers + "' Successfully started operation")
Write-Host "Started Operation Successfully"
}
Else {
Log("'" + $ComputerName.servers + "'Servers are not pinging")
Write-Host "Servers are not pinging"
}
}
For the speed effort for pinging, try this one out and compare speeds relative to how you are doing/getting from yours.
Final Super-Fast Ping Command
function Test-OnlineFast
{
param
(
# make parameter pipeline-aware
[Parameter(Mandatory,ValueFromPipeline)]
[string[]]
$ComputerName,
$TimeoutMillisec = 1000
)
begin
{
# use this to collect computer names that were sent via pipeline
[Collections.ArrayList]$bucket = #()
# hash table with error code to text translation
$StatusCode_ReturnValue =
#{
0='Success'
11001='Buffer Too Small'
11002='Destination Net Unreachable'
11003='Destination Host Unreachable'
11004='Destination Protocol Unreachable'
11005='Destination Port Unreachable'
11006='No Resources'
11007='Bad Option'
11008='Hardware Error'
11009='Packet Too Big'
11010='Request Timed Out'
11011='Bad Request'
11012='Bad Route'
11013='TimeToLive Expired Transit'
11014='TimeToLive Expired Reassembly'
11015='Parameter Problem'
11016='Source Quench'
11017='Option Too Big'
11018='Bad Destination'
11032='Negotiating IPSEC'
11050='General Failure'
}
# hash table with calculated property that translates
# numeric return value into friendly text
$statusFriendlyText = #{
# name of column
Name = 'Status'
# code to calculate content of column
Expression = {
# take status code and use it as index into
# the hash table with friendly names
# make sure the key is of same data type (int)
$StatusCode_ReturnValue[([int]$_.StatusCode)]
}
}
# calculated property that returns $true when status -eq 0
$IsOnline = #{
Name = 'Online'
Expression = { $_.StatusCode -eq 0 }
}
# do DNS resolution when system responds to ping
$DNSName = #{
Name = 'DNSName'
Expression = { if ($_.StatusCode -eq 0) {
if ($_.Address -like '*.*.*.*')
{ [Net.DNS]::GetHostByAddress($_.Address).HostName }
else
{ [Net.DNS]::GetHostByName($_.Address).HostName }
}
}
}
}
process
{
# add each computer name to the bucket
# we either receive a string array via parameter, or
# the process block runs multiple times when computer
# names are piped
$ComputerName | ForEach-Object {
$null = $bucket.Add($_)
}
}
end
{
# convert list of computers into a WMI query string
$query = $bucket -join "' or Address='"
Get-WmiObject -Class Win32_PingStatus -Filter "(Address='$query') and timeout=$TimeoutMillisec" |
Select-Object -Property Address, $IsOnline, $DNSName, $statusFriendlyText
}
}
Test-OnlineFast -ComputerName google.de, powershellmagazine.com, 10.10.10.200, 127.0.0.1
<#
# Results
Address Online DNSName Status
------- ------ ------- ------
127.0.0.1 True DESKTOP-7AAMJLF Success
google.de True google.de Success
powershellmagazine.com True powershellmagazine.com Success
10.10.10.200 False Request Timed Out
#>
Test-Connection with an array of targetnames and the asjob parameter is fast (powershell 5.1). Unresponsive addresses have a null responsetime. Annoyingly, the property names don't match the column headings.
$list = 'microsoft.com','yahoo.com'
$results = test-connection $list -count 1 -asjob | receive-job -wait
$results | select address,responsetime
address responsetime
------- ------------
yahoo.com 39
microsoft.com

Foreach loop not running through list of entries

I've tried searching this multiple times but havent resolved the issue. I have a list of servers in $mediaagentlist, the foreach loop is supposed to run through each one and get the state of certain services:
$mediaagentlist = "cs0400ma01
cs0400ma02"
[string]$Commcell_Input = $args[0]
$MAChoice = $args[1]
if ($MAChoice -eq $null)
{
Write-Output "No media agent was specified, running against all MAs in the Commcell..."
#Run this loop for each MA which is stored in Mediaagentlist
foreach ($Mediaagent in $Mediaagentlist)
{
Write-Output $Mediaagent
$GxCLMgrS_State = Invoke-Command -ComputerName $Mediaagent {Get-Service -name "GxClMgrS(Instance001)"}
$GXMMM_State = Invoke-Command -ComputerName $Mediaagent {Get-Service -name "GXMMM(Instance001)"}
$GxCVD_State = Invoke-Command -ComputerName $Mediaagent {Get-Service -name "GxCVD(Instance001)"}
Write-Output "Client manager service state: " $GxCLMgrS_State.Status
Write-Output "Media manager mount service state: " $GXMMM_State.Status
Write-Output "Communications service state: " $GxCVD_State.Status
}
}
How do I correct this so the for each loop runs through the list of entries in $mediaagentlist and runs the code for each server?
Thanks in advance!
It looks like your issue is here
$mediaagentlist = "cs0400ma01
cs0400ma02"
It looks like your missing a closing qoute per word and a comma
try
$mediaagentlist = "cs0400ma01", "cs0400ma02"
or
$mediaagentlist = #("cs0400ma01", "cs0400ma02")
Or you can define a multi-line string, then split on new lines, if there's a reason you needed the input to be a single multi-line string:
$mediaagentlist = "cs0400ma01
cs0400ma02" -split '[\r\n]+'

perform nslookup from PowerShell

I'm writing a powershell to exact the ip from a server name, which need me to embed the nslookup code into my powershell
how can I do the intergrating work?
Can any body help me?
Add-PSSnapin Microsoft.SharePoint.PowerShell
$web = Get-SPWeb -Identity “http://nycs00058260/sites/usitp“
$server_status = "PROD"
$list=$web.Lists[”DNS_Status”]
$items = $list.items
Foreach($item in $items){
$item_name = $item["Server_name"] #need to get the ip by this name
/*nslook up*/
$item_name.update()
}
If you install the PSCX module, it comes with a cmdlet Resolve-Host which handles name lookups.
Absent that, this one-liner will do the job
[System.Net.Dns]::GetHostAddresses("www.msn.com")
You can also pass in an IP address - but the results will be different.
See also http://blogs.msdn.com/b/powershell/archive/2006/06/26/647318.aspx & http://powershell.com/cs/media/p/210.aspx
PowerShell 3.0 on Windows 8 and higher comes with a Resolve-DnsName cmdlet that will get this information:
(Resolve-DnsName $server_name)[0].IpAddress
Simply use :
Resolve-DnsName monServer | ? { # make selection here } | % { $_.IPAdress } | select-object -first 1
#Here is a far better method for nslookup
# HOWTO ensure an nslookup results no errors but still gives the original names and column separations
$day = Get-Date -Format yyyyMMdd #<-- HOWTO set the day variable for today
$ErrorActionPreference = ‘SilentlyContinue’ #<-- HOWTO turn off error messages
$WarningActionPreference = 'SilentlyContinue' #<-- HOWTO turn off warning messages
$servers = Get-Content .\input\servers.txt
Foreach ($server in $servers){
$result = Resolve-DnsName $server -Server $env:LOGONSERVER.Remove(0,2) -Type ALL #<-- NSLOOKUP using your logon server
write-host ($server+","+$result.Name+","+$result.IPAddress) #<-- HOWTO Write two variables separated by a comma
}
$ErrorActionPreference = ‘SilentlyContinue’ #HOWTO turn on error messages
$WarningActionPreference = 'SilentlyContinue' #HOWTO turn on warning messages