PowerShell function to check the health of remote disks not actually remoting - powershell

I am trying to use a script to check the health of physical disks on Lenovo computers utilizing the storcli tool. I found this and have tried to modify it to be a function and allow the input of remote computers and eventually use Get-Content to input a server list. For whatever reason it takes the computer input from -ComputerName but does not actually run the commands on the remote computer. It seems to just read the disks on the local machine and always reports back "Healthy" while I know there are bad disks on the remote machine. Also I have run the script on the machine with the bad disks and it does work and reports the disk as failed. Could anyone offer any insight into what I am missing for this to actually check the remote machines? Remoting is enabled as I can run other scripts without issue. Thank you in advance.
Function Get-DriveStatus {
[cmdletbinding()]
param(
[string[]]$computername = $env:computername,
[string]$StorCLILocation = "C:\LenovoToolkit\StorCli64.exe",
[string]$StorCliCommand = "/c0/eall/sall show j"
)
foreach ($computer in $computername) {
try {
$ExecuteStoreCLI = & $StorCliLocation $StorCliCommand | out-string
$ArrayStorCLI= ConvertFrom-Json $ExecuteStoreCLI
}catch{
$ScriptError = "StorCli Command has Failed: $($_.Exception.Message)"
exit
}
foreach($PhysicalDrive in $ArrayStorCLI.Controllers.'Response Data'.'Drive Information'){
if(($($PhysicalDrive.State) -ne "Onln") -and ($($PhysicalDrive.State -ne "GHS"))) {
$RAIDStatus += "Physical Drive $($PhysicalDrive.'DID') With Size $($PhysicalDrive.'Size') is $($PhysicalDrive.State)`n"
}
}
#If the variables are not set, We’re setting them to a “Healthy” state as our final action.
if (!$RAIDStatus) { $RAIDStatus = "Healthy" }
if (!$ScriptError) { $ScriptError = "Healthy" }
if ($ScriptError -eq "Healthy")
{
Write-Host $computer $RAIDStatus
}
else
{
Write-Host $computer "Error: ".$ScriptError
}
}#End foreach $computer
}#End function
$RAIDStatus = $null
$ScriptError = $null

Related

While Loop with Break Statement in PowerShell [duplicate]

I am trying to build my own script to check some Windows services (status and start mode) and I am facing an issue on the IF ...
For example even if the service is "Running", it will never run the code inside the IF...
let me share my code below (I am a newbie on powershell so be gentle xD)
For info, I will do more actions inside the IF and ELSE, it is just for the example.
# import computers list, 1 by line
$Computers = get-content .\computers.txt
# define variable of services we want to check
$ServiceNetbios = "netbt"
# define variable to ask credentials
$Cred = Get-Credential
# declare Function to open a session a remote computer
Function EndPSS { Get-PSSession | Remove-PSSession }
EndPSS
########################################################
# BEGINNING OF SCRIPT #
# by xxx #
# 2022-02-03 #
########################################################
# loop for each computer imported from the file
foreach ($computer in $computers) {
# show name of computer in progress
$computer
# connect remotely to the computer
$session = New-PSSession -ComputerName $computer -Credential $Cred
# check Netbios service
$StatusServiceNetbios = Invoke-Command -Session $session -ScriptBlock { Get-Service -Name $Using:ServiceNetbios | select -property * }
# Check Netbios service started or not
write-host $StatusServiceNetbios.Status
if ($StatusServiceNetbios.Status -eq 'Running')
{
Write-host "IF Running"
}
else
{
write-host "IF NOT Running"
}
EndPSS
}
and what return my script :
computername
Running (<= the variable $StatusServiceNetbios.Status )
IF NOT Running (<= the ELSE action)
Thanks you in advance for your help,
this drive me crazy and maybe this is very simple...
To complement Cpt.Whale's helpful answer, this is likely to be caused by the serialization and deserialization done by Invoke-Command:
using namespace System.Management.Automation
$service = Get-Service netbt
$afterInvokeCmd = [PSSerializer]::Deserialize(([PSSerializer]::Serialize($service)))
$service.Status -eq 'Running' # => True
$afterInvokeCmd.Status -eq 'Running' # => False
$afterInvokeCmd.Status.Value -eq 'Running' # => True
$afterInvokeCmd.Status.ToString() -eq 'Running' # => True
To put some context to my answer, this is a nice quote from about_Remote_Output that can better explain why and what is happening:
Because most live Microsoft .NET Framework objects (such as the objects that PowerShell cmdlets return) cannot be transmitted over the network, the live objects are "serialized". In other words, the live objects are converted into XML representations of the object and its properties. Then, the XML-based serialized object is transmitted across the network.
On the local computer, PowerShell receives the XML-based serialized object and "deserializes" it by converting the XML-based object into a standard .NET Framework object.
However, the deserialized object is not a live object. It is a snapshot of the object at the time that it was serialized, and it includes properties but no methods.
This is probably because of the way powershell creates service objects - (Get-Service netbt).Status has a child property named Value:
$StatusServiceNetbios.Status
Value
-----
Running
# so Status is never -eq to 'Running':
$StatusServiceNetbios.Status -eq 'Running'
False
# use the Value property in your If statement instead:
$StatusServiceNetbios.Status.Value -eq 'Running'
True

How can I check if the PowerShell profile script is running from an SSH session?

I'm trying to work around a bug in Win32-OpenSSH, where -NoProfile -NoLogo is not respected when using pwsh.exe (Core) and logging in remotely via SSH/SCP. One way (of several) I tried, was to add the following in the very beginning of my Microsoft.PowerShell_profile.ps1 profile.
function IsInteractive {
$non_interactive = '-command', '-c', '-encodedcommand', '-e', '-ec', '-file', '-f'
-not ([Environment]::GetCommandLineArgs() | Where-Object -FilterScript {$PSItem -in $non_interactive})
}
# No point of running this script if not interactive
if (-not (IsInteractive)) {
exit
}
...
However, this didn't work with a remote SSH, because when using [Environment]::GetCommandLineArgs() with pwsh.exe, all you get back is:
C:\Program Files\PowerShell\6\pwsh.dll
regardless whether or not you are in an interactive session.
Another way I tried, was to scan through the process tree and look for the sshd parent, but that was also inconclusive, since it may run in another thread where sshd is not found as a parent.
So then I tried looking for other things. For example conhost. But on one machine conhost starts before pwsh, whereas on another machine, it starts after...then you need to scan up the tree and maybe find an explorer instance, in which case it is just a positive that the previous process is interactive, but not a definite non-interactive current process session.
function showit() {
$isInter = 'conhost','explorer','wininit','Idle',
$noInter = 'sshd','pwsh','powershell'
$CPID = ((Get-Process -Id $PID).Id)
for (;;) {
$PNAME = ((Get-Process -Id $CPID).Name)
Write-Host ("Process: {0,6} {1} " -f $CPID, $PNAME) -fore Red -NoNewline
$CPID = try { ((gwmi win32_process -Filter "processid='$CPID'").ParentProcessId) } catch { ((Get-Process -Id $CPID).Parent.Id) }
if ($PNAME -eq "conhost") {
Write-Host ": interactive" -fore Cyan
break;
}
if ( ($PNAME -eq "explorer") -or ($PNAME -eq "init") -or ($PNAME -eq "sshd") ) {
# Write-Host ": non-interactive" -fore Cyan
break;
}
""
}
}
How can I check if the profile script is running from within a remote SSH session?
Why am I doing this? Because I want to disable the script from running automatically through SSH/SCP/SFTP, while still being able to run it manually (still over SSH.) In Bash this is a trivial one-liner.
Some related (but unhelpful) answers:
Powershell test for noninteractive mode
How to check if a Powershell script is running remotely

Including common code in header/footer with PowerShell

I have common functions and formats to most of my scripts. Each script brings up a window for me to paste workstations and it performs basic tasks like checking connectivity before proceeding. Generally, I copy and paste this code and modify the body. What I would like to do is include a header and footer, but I get "Missing closing '}' in statement block." errors. Example:
<# Begin Header #>
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
<# End Header #>
Body of script
<# Begin Footer #>
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
<# End Footer #>
Rather than copying/pasting each time, I would prefer to do this...
."c:\scripts\header.ps1"
-- code --
."c:\scripts\footer.ps1"
Is that even possible when the header ends with an open bracket? I do this in PHP but I can't figure out a work-around in PowerShell.
Your approach could be changed into storing a function in one file and your custom script that runs for-each server in another. You can store a scriptblock to a variable in PowerShell and pass that as a parameter to a function. You can use Invoke-Command -scriptblock $Variable to execute that code.
Write your function like this:
function runAgainstServerList {
param ( [ScriptBlock]$ScriptBlock)
if($canceled) {
write-host "Operation canceled."
}
else {
if($computers.length -gt 0) {
[array]$computers = $computers.split("`n").trim()
# Loop through computers entered
foreach($pc in $computers) {
# Skip zero length lines for computers
if(($pc.length -eq $null) -OR ($pc.length -lt 1)) {
continue
}
else {
# Try to connect to the computer, otherwise error and continue
write-host "Connecting to: $pc$hr"
if(test-connection -computername $pc -count 1 -ea 0) {
Invoke-Command -ScriptBlock $ScriptBlock
}
else {
utc # Unable to contact
}
}
write-host "`n"
}
}
}
}
Now save that off to your include file like 'myFunctions.ps1'
Then create your custom script that you want to run per server like this:
. myFunctions.ps1
[ScriptBlock]$ScriptBlockToPass = {
## Insert custom code here
}
runAgainstServerList $ScriptBlockToPass
To get you a step closer to what might be your end goal, You may want to append the -ComputerName "ComputerNameHere" argument to your invoke-command statement inside your included function. This would cause your script to be executed on the remote system instead of locally.

What's the fastest way to get online computers

I'm writing a function which returns all Online Computers in our network, so I can do stuff like this:
Get-OnlineComputers | % { get-process -computername $_ }
Now I basically got my function ready, but it's taking way too long.
I want to only return Computers which have WinRM active, but I also want to provide the option to get every computer even those which haven't got WinRM set up (switch parameter).
This is my function. first it creates a pssession to the domaincontroller, to get all computers in our LAN. then foreach computer, it will test if they have WinRM active or if they accept ping. if so, it gets returned.
$session = New-PSSession Domaincontroller
$computers = Invoke-Command -Session $session { Get-ADComputer -filter * } | select -ExpandProperty Name
$computers | % {
if ($IncludeNoWinRM.IsPresent)
{
$ErrorActionPreference = "SilentlyContinue"
$ping = Test-NetConnection $_
if ($ping.PingSucceeded -eq 'True')
{
$_
}
}
else
{
$ErrorActionPreference = "SilentlyContinue"
$WinRM = Test-WSMan $_
if ($WinRM)
{
$_
}
}
}
Is this the best way I can go to check my online computers? Does anyone have a faster and better idea?
Thanks!
Very Quick Solution is using the -Quiet Parameter of the Test-Connection cmdlet:
so for example:
$ping = Test-Connection "Computer" -Quiet -Count 1
if ($ping)
{
"Online"
}
else
{
"Offline"
}
if it's not enough fast for you, you can use the Send Method of the System.Net.NetworkInformation.Ping
here's a sample function:
Function Test-Ping
{
Param($computer = "127.0.0.1")
$ping = new-object System.Net.NetworkInformation.Ping
Try
{
[void]$ping.send($computer,1)
$Online = $true
}
Catch
{
$Online = $False
}
Return $Online
}
Regarding execute it on multiple computers, I suggest using RunSpaces, as it's the fastest Multithreading you can get with PowerShell,
For more information see:
Runspaces vs Jobs
Basic Runspaces implemenation
Boe Prox (master of runspaces) has written a function which is available from the Powershell Gallery. I've linked the script below.
He uses many of the answers already given to achieve the simultaneous examination of 100s of computers by name. The script gets WMI network information if test-connection succeeds. It should be fairly easy to adapt to get any other information you want, or just return the result of the test-connection.
The script actually uses runspace pools rather than straight runspaces to limit the amount of simultaneous threads that your loop can spawn.
Boe also wrote the PoSH-RSJob module already referenced. This script will achieve what you want in native PoSH without having to install his module.
https://gallery.technet.microsoft.com/scriptcenter/Speedy-Network-Information-5b1406fb

Checking several Hyper-V Hosts for a specific VM in Powershell

I am writing a script to administer Hyper-VMs using the PowerShell Management Library for Hyper-V.
Since we are using several Hyper-V Hosts and our VMs can change their host for performance reasons or other reasons I need a script that finds out which Host a VM runs on for the following functions.
This was my try at accomplishing this:
function IdentifyHost
{
param
(
[parameter(Position=0, Mandatory = $true)]
[ValidateNotNullOrEmpty()]
$VM
)
[Array]$hosts=Get-VMHost
if ($hosts.count -eq 0)
{
Write-Warning "No valid hosts found."
}
for ([int]$i=0; $i -lt $hosts.count; $i++ )
{
try
{
$out = Get-VM -Name $VM -Server $hosts[$i] -ErrorAction Stop
}
catch [UnauthorizedAccessException]
{
Write-Warning "Access to $hosts[$i] denied."
}
if ($VM -is [String])
{
if ($out.VMElementName -eq $VM )
{
return $out.__SERVER
}
}
elseif ($VM.ElementName -ne $null)
{
if ($out.VMElementName -eq $VM.VMElementName)
{
return $out.__SERVER
}
}
}
Write-Warning "No Host found for $VM"
}
Get-VMHost returns an array of all available Hyper-V hosts in the local area network.
My problem is that my function always returns the first element of the $hosts array whenever there is an UnauthorizedAccessException for the first element.
The plan is as following:
If the VM exists on the Host he will return a WMI Object representing that VM whose VMElementName property is equal to the VMs name given as parameter.
If the VM is given a WMI Object representing a VM the VMElementName properties of the two objects are equal.
If the VM does not exist on the Host he returns nothing.
If there's an access issue it should be catched.
But somehow it doesn't work out.
My question is this: What am I doing wrong in the code? And how can I fix it?
EDIT: The output of the function is the access problem warning for the first element of the $hosts array and then the first element of $hosts itself.
EDIT2: I fixed this myself by changing the return from the fragile $hosts[$i] to $out.__Server
Okay so I found a possible way of solving this issue:
Instead of returning the $hosts[$i] which yields unfavorable results I return the __Server property of $out, assuming there is a valid $out that matches the conditions.
If any of you guys knows a better or cleaner way of doing this, please by my guest.