Restart-Computer to Email Body = Null, PowerShell - powershell

Attempting to run the follow script for a reboot I get the following error,
"Send-MailMessage : Cannot validate argument on parameter 'Body'. The argument is null or empty. Supply an arg
that is not null or empty and then try the command again.
At line:8 char:30
+ Send-MailMessage #messageParameters -BodyAsHtml
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SendMailMessage"
Any help would be great!
Thanks
Cody
Restart-Computer -ComputerName nocconverter1 -Wait -For Wmi
$server = 'XXX'
ping -n 2 $server >$null
Function Server_Status_Check {
if($lastexitcode -eq 0) {
write-host "$server is ONLINE"
} else {
write-host "$server is OFFLINE/UNREACHABLE"
}
}
$messageParameters = #{
Subject = "Result: Reboot report for WebPeriop - $((Get-Date).ToShortDateString())"
Body = Server_Status_Check | out-string
From = "XXXX"
To = "XXXXX"
SmtpServer = "exmbx6"
}
Send-MailMessage #messageParameters -BodyAsHtml

Write-Host writes directly to the display. It doesn't write to stdout out therefore your Server_Status_Check function outputs nothing. Change it to:
Function Server_Status_Check {
if($lastexitcode -eq 0) {
"$server is ONLINE"
}
else {
"$server is OFFLINE/UNREACHABLE"
}
}

Related

PowersShell script doesn't work remotely with Invoke-Command but works perfectly locally

I have a script to check for and download windows updates. It isn't working remotely and I would like to know why. Here is the script:
$UpdateSession = New-Object -Com Microsoft.Update.Session
$updatesToDownload = New-Object -Com Microsoft.Update.UpdateColl
$updatesToInstall = New-Object -Com Microsoft.Update.UpdateColl
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'")
$Downloader = $UpdateSession.CreateUpdateDownloader()
$Installer = $UpdateSession.CreateUpdateInstaller()
# Check for updates ---------------------------------------------------------------------
if ($SearchResult.Updates.Count -gt 0) {
Write-Host("All updates found: " + $SearchResult.Updates.Count)
For ($X = 0; $X -lt $SearchResult.Updates.Count; $X++) {
$Update = $SearchResult.Updates.Item($X)
if ($Update.KBArticleIDs -eq '2267602') {
Write-Host("Updates with Ids 2267602 found: " + $Update.Title)
$updatesToDownload.Add($Update)
Write-Host("Update " + $Update.Title + " added to download list")
}
}
}
else {
# Write-Host(0) # No Security Intelligence Updates
Write-Host("No update found")
Exit
}
# Download updates ----------------------------------------------------------------------
if ($updatesToDownload.Count -gt 0) {
Write-Host("Start download process")
$Downloader.Updates = $updatesToDownload
$DownloadResult = $Downloader.Download()
Write-Host("Download ResultCode: " + $DownloadResult.ResultCode)
if ($DownloadResult.ResultCode -eq 2) {
For ($X = 0; $X -lt $updatesToDownload.Count; $X++) {
Write-Host("Adding updates to install list")
$Update = $updatesToDownload.Item($X)
if ($update.IsDownloaded -eq 'True') {
$updatesToInstall.Add($Update)
Write-Host("Update " + $Update.Title + " added to install list")
}
}
}
}
else {
# Write-Host(0) # No Security Intelligence Updates
Write-Host("No update to download found")
Exit
}
The script works perfectly when I run it on a computer using administrator account.
But when I try to run it remotely using below short script and the same administrator credentials it starts and do "Check for updates" bloc and stops with error.
$password = ConvertTo-SecureString "some_password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential("computer_name\administrator", $password)
$sess = New-PSSession -ComputerName "computer_name" -Credential $cred
Invoke-Command -Session $sess -FilePath "D:\Test.ps1"
Where "D:\Test.ps1" is path to a file with script mentioned above.
Error message:
The property 'Updates' cannot be found on this object. Verify that the property exists and can be set.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFound
+ PSComputerName : computer_name
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : computer_name
I have checked remote credentials using other script (below) and the result is True yet updates script doesn't work.
[bool] (net session 2>$null)
[Security.Principal.WindowsPrincipal]::new(
[Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
What did I do wrong? Why doesn't the script work remotely?

How to change output of Invoke-WebRequest?

I want to be able to get the results of Invoke-WebRequest and have my script print either "Failed" if the server was not reached or "Online" if it was reached.
This is what I'm doing to try to do that.
$IW_Results = $Servers_to_Check | ForEach-Object { Invoke-WebRequest -Uri $_ }
$err = $IW_Results | ?{$_.gettype().Name -eq "ErrorRecord"}
if($err){
Write-Output "Failed"
}
else {
Write-Output "Online"
}
I was able to get the script to print "Online" if the server is reached. However when it can't be reached, my script wont print "Failed". Instead it will give me the error:
Invoke-WebRequest : Unable to connect to the remote server
At C:\Users\admin\Documents\VM-scripts\VM-tester.ps1:32
char:52
+ ... ts = $Servers_to_Check | ForEach-Object { Invoke-WebRequest -Uri $_ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:Htt
pWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShe
ll.Commands.InvokeWebRequestCommand
How can I get the script to print out "Failed" instead of this error message?
Also the $Servers_to_Check variable is multiple servers
You need a Try Catch
$Servers_to_Check = "google.com", "asdasdf.asdfaa.sdf","yahoo.com"
$IW_Results = $Servers_to_Check | ForEach-Object {
try{
Invoke-WebRequest -Uri $_ | Out-Null
"Online"
}catch{
"Failed"
}
}
$IW_Results

How to remove user profiles with PowerShell

I have the script below. If I uncomment the line commented #3, I get the error
Exception calling "Delete" with "0" argument(s): ""
At Z:\Scripts\Powershell\Remove-UserProfile.ps1:48 char:21
+ $Profile.Delete()
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
regardless of whether I interrogate WMI using the format in #1 or #2. If I leave #3 commented, and uncomment #4, I get the error
Remove-WmiObject :
At Z:\Scripts\Powershell\Remove-UserProfile.ps1:49 char:21
+ Remove-WmiObject -InputObject $Profile
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Remove-WmiObject], COMException
+ FullyQualifiedErrorId : RemoveWMICOMException,Microsoft.PowerShell.Commands.RemoveWmiObject
regardless of the query string for Get-WMIObject.
Everything I can find on the web - including a couple of other SO questions - implies that either of these methods should work, but neither seems to. I have checked to see if the target profile is loaded, and it is not. Why can I not use WMI to delete user profiles? What can I do that does work, and doesn't involve downloading a utility from a third-party (which is not permitted by our "information security" team)?
Script:
function Remove-UserProfile {
<#
.SYNOPSIS
Removes user profiles from computers
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName = $env:ComputerName,
[Alias("UserName","sAMAccountName")]
[String]$Identity,
[Int]$Age,
[Switch]$DomainOnly
)
BEGIN {
$NoSystemAccounts = "SID!='S-1-5-18' AND SID!='S-1-5-19' AND SID!='S-1-5-20' " # Don't even bother with the system accounts.
if ($DomainOnly) {
$SIDQuery = "SID LIKE '$((Get-ADDomain).DomainSID)%' " # All domain account SIDs begin with the domain SID
} elseif ($Identity.Length -ne 0) {
$SIDQuery = "SID LIKE '$(Get-UserSID -AccountName $Identity)' "
}
$CutoffDate = (Get-Date).AddDays(-$Age)
$Query = "SELECT * FROM Win32_UserProfile "
}
PROCESS{
ForEach ($Computer in $ComputerName) {
Write-Verbose "Processing Computer $Computer..."
if ($SIDQuery) {
$Query += "WHERE " + $SIDQuery
$FilterStr = $SIDQuery
} else {
$Query += "WHERE " + $NoSystemAccounts
$FilterStr = $NoSystemAccounts
}
Write-Verbose "Querying WMI using '$Query' and filtering for profiles last used before $CutoffDate ..."
#1 $Profiles = Get-WMIObject -Query $Query | Where-Object { [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
#2 $Profiles = Get-WMIObject -ComputerName $Computer -Class Win32_UserProfile -Filter $FilterStr | Where-Object { [Management.ManagementDateTimeConverter]::ToDateTime($_.LastUseTime) -lt $CutoffDate }
ForEach ($Profile in $Profiles) {
if ($PSCmdlet.ShouldProcess($Profile)) {
#3 $Profile.Delete()
#4 Remove-WmiObject -InputObject $Profile
}
}
}
}
END {}
}
Edit:
You have to run the script as Administrator.

PS Get-WinEvent throw 'The Handle is invalid'

I have a list of hostnames from which I'd like to extract all AppLocker related eventlogs, especially the ones with level warning and/or error.
I crafted this script:
$ComputersToCheck = Get-Content 'X:\ListWithTheNames.txt'
foreach($OneHost in $ComputersToCheck)
{
try
{
$EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ComputerName $OneHost -Credential $CredentialFromUser
foreach ($SingelEvent in $EventCollection)
{
if($SingelEvent.LevelDisplayName -ne "Information")
{
$pathtosaveto = 'SomeFileName.txt'
$ResultString += $SingelEvent | Select Message,MachineName,UserId | Export-Csv -Path $pathtosaveto -Append
}
}
}
catch
{
//handling exceptions
}
}
This works for a while, but after a certain ammount of time I got an error:
Get-WinEvent : The remote procedure call failed
At X:\FileName.ps1:22 char:28
+ $EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EX ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The remote procedure call failed,Microsoft.PowerShell.Commands.GetWinEventCommand
And right after the script start giving errors like this:
Get-WinEvent : The handle is invalid
At X:\FileName.ps1:22 char:28
+ $EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EX ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-WinEvent], EventLogException
+ FullyQualifiedErrorId : The handle is invalid,Microsoft.PowerShell.Commands.GetWinEventCommand
My first thought was that it is related to the host the script try to reach, but the next in the list is the same type (Os, even the same model) as the previous.
I ran the script 3 times, and every time the output size was different (probably because not the same hosts were online with the same amount of logs).
The script should run against more than 700 hosts, to which a special account is needed which I prompt by the Get-Credential, store in a variable and pass it the the Get-WinEvent as a parameter.
To be honest I stuck with this issue, not really sure what cause this and why.
If anyone has an idea please share with me :)
Give this a try to attempt catching references to failed hosts and empty objects. You could write the exception received but I didn't include that in this to make the failedhosts file simple to read. Hope I got it right as I winged it and don't have a true case to test against.
$ComputersToCheck = Get-Content 'X:\ListWithTheNames.txt'
foreach($OneHost in $ComputersToCheck) {
try {
$EventCollection = Get-WinEvent -LogName "Microsoft-Windows-AppLocker/EXE and DLL" -ComputerName $OneHost -Credential $CredentialFromUser -ErrorAction Stop
if($EventCollection) {
foreach ($SingelEvent in $EventCollection) {
if($SingelEvent.LevelDisplayName -ne "Information") {
$pathtosaveto = 'SomeFileName.txt'
$ResultString += $SingelEvent | Select Message,MachineName,UserId | Export-Csv -Path $pathtosaveto -Append
}
}
} else {
Out-File -InputObject $($OneHost + " Empty Event Collection") -FilePath "C:\FailedHosts.txt" -Append -Encoding ascii
}
}
catch {
Out-File -InputObject $($OneHost + " Failed Connection") -FilePath "C:\FailedHosts.txt" -Append -Encoding ascii
}
}

Catching dynamic exception types in PowerShell

I'm working on a PowerShell library that automates some network management operations. Some of these operations have arbitrary delays, and each can fail in unique ways. To handle these delays gracefully, I'm creating a generic retry function that has three main purposes:
Execute an arbitrary command (with parameters)
If it fails in a recognized way, try it again, up to some limit
If it fails in an unexpected way, bail and report
The problem is item #2. I want to be able to specify the expected exception type for the command. How can I do this in PowerShell?
Here's my function:
Function Retry-Command {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, Position=0)]
[String] $name,
[Parameter(Mandatory=$True, Position=1)]
[String] $scriptBlock,
[String[]] $argumentList,
[Int] $maxAttempts=3,
[Int] $retrySeconds=10,
[System.Exception] $retryException=[System.Management.Automation.RuntimeException]
)
$attempts = 1
$keepTrying = $True
$cmd = [ScriptBlock]::Create($scriptblock)
do {
try {
&$cmd #argumentList
$keepTrying = $False
Write-Verbose "Command [$commandName] succeeded after $attmpts attempts."
} catch [$retryException] {
$msg = "Command [$commandName] failed. Attempt $attempts of $maxAttempts."
Write-Verbose $msg;
if ($maxAttempts -gt $attempts) {
Write-Debug "Sleeping $retrySeconds"
Start-Sleep -Seconds $retrySeconds
} else {
$keepTrying = $False
Write-Debug "Reached $attempts attempts. Re-raising exception."
Throw $_.Exception
}
} catch [System.Exception] {
$keepTrying = $False
$msg = "Unexpected exception while executing command [$CommandName]: "
Write-Error $msg + $_.Exception.ToString()
Throw $_.Exception
} finally {
$attempts += 1
}
} while ($True -eq $keepTrying)
}
I call it like this:
$result = Retry-Command -Name = "Foo the bar" -ScriptBlock $cmd -ArgumentList $cmdArgs
But this is the result:
Retry-Command : Cannot process argument transformation on parameter 'retryException'.
Cannot convert the "System.Management.Automation.RuntimeException" value of type "System.RuntimeType" to type "System.Exception".
At Foo.ps1:111 char:11
+ $result = Retry-Command <<<< -Name "Foo the bar" -ScriptBlock $cmd -ArgumentList $cmdArgs
+ CategoryInfo : InvalidData: (:) [Retry-Command], ParameterBindin...mationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Retry-Command
This seems to be saying that the type of [System.Management.Automation.RuntimeException] is not itself a [System.Exception], but is instead a [System.RuntimeType] which makes sense.
So, how do I specify the type of the exception to be caught?
It's not possible to use a variable as a catch criteria, it has to be a type-object (or something), everything else gives you an error. A workaround would be something like this:
#You can get the name of the exception using the following (or .Name for just the short name)
#PS > $myerr.Exception.GetType().Fullname
#System.UnauthorizedAccessException
function test {
param(
#Validate that specified name is a class that inherits from System.Exception base class
[ValidateScript({[System.Exception].IsAssignableFrom([type]$_)})]
$ExceptionType
)
try {
#Test-script, Will throw UnauthorizedAccessException when not run as admin
(Get-Content C:\test.txt) | % { $_ -replace 'test','lol' } | Set-Content C:\test.txt
}
catch [System.Exception] {
#Check if exceptiontype is equal to the value specified in exceptiontype parameter
if($_.Exception.GetType() -eq ([type]$ExceptionType)) {
"Hello. You caught me"
} else {
"Uncaught stuff: $($_.Exception.Gettype())"
}
}
}
A few tests. One with non-existing type, then with non-exception type, and finally a working one
PS > test -ExceptionType system.unaut
test : Cannot validate argument on parameter 'ExceptionType'. Cannot convert the "system.unaut" val
ue of type "System.String" to type "System.Type".
At line:1 char:21
+ test -ExceptionType system.unaut
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,test
PS > test -ExceptionType String
test : Cannot validate argument on parameter 'ExceptionType'. The "[System.Exception].IsAssignableF
rom([type]$_)" validation script for the argument with value "String" did not return true. Determin
e why the validation script failed and then try the command again.
At line:1 char:21
+ test -ExceptionType String
+ ~~~~~~
+ CategoryInfo : InvalidData: (:) [test], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,test
PS > test -ExceptionType UnauthorizedAccessException
Hello. You caught me