Powershell script exits on run time when Invoke-expression is executed - powershell

I am trying to execute below code, when the run time hits the first invoke-expression , the script exit with out running the remaining code .ps1 file . This is happening in windows 7 with powershell v3. Any pointers here is appreciated. I have tried using Try{Invoke-expression ""} catch{ $_ }, but the logs show that script exit.
$HardWares=#("abc","def")
Write-Info ("Deleting device driver with $application")
foreach ($HardWare in $HardWares){
Write-Info ("working on hardware $HardWare")
$DriverID = (Get-WmiObject -Class Win32_PnpSignedDriver | Where-Object {$_.hardwareID -eq $HardWare} | Select-Object InfName -ExpandProperty InfName | Select-Object -Unique)
if ($DriverID){
Write-Info ("Removing $HardWare and deleting $DriverID")
$HardwareRemoveCmd = "D:\Users\App.exe remove $HardWare"
Invoke-Expression $HardwareRemoveCmd
$Command= "D:\Users\App.exe dp_delete $DriverID -f"
Invoke-Expression $Command
} else {Write-Info "Could not find $DriverID file for $HardWare"}
}

The issue might be that you've created a variable called $DriverID, yet you're checking if $Driverid returns true. Variable creation is case sensitive so you have two different variables here. This will return false causing the script to skip over into the else statement.
If you want "did not find any $Driverid" to be written to the display, you'll need to put "Write-Host" in front of it.
A tidied up version of your code.
{
$Driverid = (Get-WmiObject -Class Win32_PnpSignedDriver | Where-Object
{$_.hardware -eq $HardWare} | Select-Object InfName -ExpandProperty InfName
| Select-Object -Unique)
if($Driverid)
{
Invoke-Expression "D:\Users\App.exe remove $HardWare"
$Command= "D:\Users\App.exe dp_delete $Driverid -f"
Invoke-Expression $Command
}
else
{
Write-Host "did not find any $Driverid"
}
}
Hope this helps!

Related

How to modify local variable within Invoke-Command

I am trying to modify a variable within Invoke-Command in order to get out of a loop, however I'm having trouble doing that.
In the sample script below, I'm connecting to a host, grabbing information from NICs that are Up and saving the output to a file (Baseline). Then on my next iteration I will keep grabbing the same info and then compare Test file to Baseline file.
From a different shell, I've connected to the same server and disabled one of the NICs to force Compare-Object to find a difference.
Once a difference is found, I need to get out of the loop, however I cannot find a way to update the local variable $test_condition. I've tried multiple things, from Break, Return, $variable:global, $variable:script, but nothing worked so far.
$hostname = "server1"
$test_condition = $false
do {
Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
$path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"
if ($path -eq $false) {
Get-NetAdapter | Where-Object Status -EQ "Up" | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)
} else {
Get-NetAdapter | Where-Object Status -EQ "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
$objects = #{
ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
}
$test_condition = (Compare-Object #objects).SideIndicator -ccontains "<="
$test_condition #this is returning True <-----
}
}
} until ($test_condition -eq $true)
Any tips? What am I doing wrong?
TIA,
ftex
You can pass variables into a remote script block with the $Using:VarName scope modifier, but you can't use typical $Global: or $Script to modify anything in the calling scope. In this scenario the calling scope isn't the parent scope. The code is technically running in a new session on the remote system and $Global: would refer to that session's global scope.
For example:
$var = "something"
Invoke-Command -ComputerName MyComuter -ScriptBlock { $Global:var = "else"; $var}
The remote session will output "else". However, after return in the calling session $var will output "something" remaining unchanged despite the assignment in the remote session.
Based on #SantiagoSquarzon's comment place the assignment inside the Do loop with a few other modifications:
$hostname = "server1"
do {
$test_condition = $false
$test_condition =
Invoke-Command -ComputerName $hostname -Credential $credential -ScriptBlock{
$path = Test-Path -LiteralPath C:\Temp\"network_list_$using:hostname-Baseline.txt"
if ($path -eq $false) {
Get-NetAdapter | Where-Object Status -eq "Up" | Out-File -FilePath (New-Item C:\Temp\"network_list_$using:hostname-Baseline.txt" -Force)
} else {
Get-NetAdapter | Where-Object Status -eq "Up" | Out-File -FilePath C:\Temp\"network_list_$using:hostname-Test.txt"
$objects = #{
ReferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Baseline.txt")
DifferenceObject = (Get-Content C:\Temp\"network_list_$using:hostname-Test.txt")
}
(Compare-Object #objects).SideIndicator -contains "<=" # this is returning True <-----
}
}
} until ($test_condition -eq $true)
I don't know why you were using -ccontains considering "<=" has no casing implications. Also it's very unusual to capitalize operators.
Notice there's no explicit return or assignment. PowerShell will emit the Boolean result of the comparison and that will be returned from the remote session and end up assigned to the $test_condition variable.
An aside:
I'm not sure why we want to use -contains at all. Admittedly it'll work fine in this case, however, it may lead you astray elsewhere. -contains is a collection containment operator and not really meant for testing the presence of one string within another. The literal meaning of "contains" makes for an implicitly attractive hazard, as demonstrated in this recent question.
In short it's easy to confuse the meaning, purpose and behavior on -contains.
This "<=" -contains "<=" will return "true" as expected, however "<==" -contains "<=" will return "false" even though the left string literally does contain the right string.
The answer, to the aforementioned question says much the same. My addendum answer offers a some additional insight for the particular problem and how different operators can be circumstantially applied.
So, as a matter of practice for this case wrap the Compare-Object command in the array sub-expression operator like:
#( (Compare-Object #objects).SideIndicator ) -contains "<="
Given the particulars, this strikes me as the least intrusive way to implement such a loosely stated best practice.

Hyper-V Powershell checkpoint creation/deletion is not synchronous. Is there a better option?

I've found myself having to write wrappers around powershell's Remove-VMSnapshot and Checkpoint-VM. The docs make no mention of it, but based on the Write-Host in both of the code snippets below executing, checkpoints are not fully deleted/created after the MS provided cmdlets. I hit this when trying to restore to a checkpoint by name immediately after creating it yielded an error.
Has anyone else encountered this? Thoughts on better ways to handle it? Tricks to prevent any of my code from calling the MS cmdlets directly?
function Remove-VMSnapshots-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName,
)
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$matchingSnapshots | Remove-VMSnapshot
do
{
$matchingSnapshots = #(Get-VMSnapshot $VM | Where-Object {$_.Name -eq $CheckpointName})
$stillThere = $matchingSnapshots.length -gt 0
if ($stillThere)
{
Write-Host 'sleeping to try to let snapshot disappear'
start-sleep -s 1
}
} while ($stillThere)
}
function Checkpoint-VM-Sync
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)][object]$VM,
[Parameter(Mandatory=$True)][string]$CheckpointName
)
$checkpoint = Checkpoint-VM -VM $VM -SnapshotName $CheckpointName -PassThru
$checkpoint | Write-Host
while (-not (#(Get-VMSnapshot $VM | Select -ExpandProperty Id)).Contains($checkpoint.Id))
{
Write-Host 'waiting for checkpoint to be in list'
Get-VMSnapshot $VM | Write-Host
start-sleep -s 1
}
}
Had a similar issue, see the answer in Can I override a Powershell native cmdlet ... it shows you how easily it is to override commands.
You need to add it into your profile (only for you), or add it to the script (for every one that runs the script), it depends on your situation.

Show output before Read-Host

I'm having some issues getting some info to write to the console before Read-Host. Let me throw out a simplified example.
Function Add-Build {
[CmdletBinding()]
Param ([Parameter(Mandatory=$True,Position=1)][String]$Build
,[Parameter(Mandatory=$False,Position=2)][System.Nullable``1[[System.Int32]]]$VersionID
,[Parameter(Mandatory=$False,Position=3)][String]$BuildDescription
)
Write-Host -BackgroundColor DarkYellow "Adding SQL Build $($Build)"
IF ($VersionID -eq $null)
{
Get-SqlVersions | Out-String
$VersionID = Read-Host -Prompt "SELECT Version (Enter To Skip)" | % { IF ($_ -eq '') {$null} ELSE {$_}}
}
}
FUNCTION Test-Function {
$BuildID = (Get-BuildID -Build "11.0.3156.0").ToString()
}
If I call Add-Build directly then the Get-SqlVersions | Out-String output before the Read-Host. If I call Test-Function though the Get-SqlVersions no longer outputs to the console at all. Get-SqlVersions makes a SQL proc call and the output is a couple Datarows.
Is there a way to ensure the Get-SqlVersions data shows up when calling Test-Function?
Make it explicitly output to the host.
$GetSQL = Get-SqlVersions | Out-String
Write-Host $GetSQL
Could you please store Get-SqlVersions | Out-String; in a Variable and display that. I think that should work.
$versions = Get-SqlVersions | Out-String;
$versions
I know this is old, but I stumbled on it and can't help but contribute.
The problem is here:
Get-SqlVersions | Out-String
Change it to this:
Get-SqlVersions | Out-Host
I did some quick looking around, and Out-String seems to collect and prepare things for displaying. Out-Host just does it.

want to see a message on powershell ISE while executing .exe

I am trying to execute this powershell script
Write-output `n "\\Latest Modified Mobile file is"
$dir = "\\sharepoint.amr.ith.intel.com#SSL\sites\peca\PTA Data Sandbox\Shared Documents\Under_review\Regina\Estimates"
$filter="*EstimateImport_Mobile_*.xlsx"
$latest = Get-ChildItem -Path $dir -Filter $filter | Sort-Object LastAccessTime -Descending | Select-Object -First 1
$latest.Name
if($?)
{
"command succeeded"
}
else
{
"command failed"
}
& "C:\PECA Data Estimates Importer\PECA Estimates Importer.exe" import -i $latest.FullName
if($?)
{
"command succeeded"
}
else
{
"command failed"
}
Start-Sleep -s 5
exit
when I am using the "&" operator, it runs some windows .exe tool but on the screen I don't see any message, I want to see a message like "Running tool" on a powershell ISE so that the user knows that the script is still working.
If you want to see output on the screen, don't use Write-Output.
Use Write-Host to write text that will always be seen. Use Write-Verbose to write text that will only be seen in verbose mode.

Powershell - Script that accepts single string param or filelist using Get-Content

I'm having trouble getting PowerShell to accept a single string param input on the command line OR accept a file as input containing a list of servers.
I've tried working this example but I can't get things to work.
I also tried working with param([switch] $FileList) but the IF statement doesn't reach the else block.
What I hope to put together is a script that accepts a single server name passed in on the command line or accepts input from a text file. I do appreciate any pointers!
-edit, using the below running the script with/without a param returns Run on single server and it is the same if (($FileList -eq $false)) Using Keith Hill's example, again no matter what I try passing the script the output is always same (the IF block never reaches the ELSE block)
-Edit2, the second code example works when passing a single server name to the script, the problem for me is trying to get the [Switch] parameter to accept a filename and pass it to the code block with the foreach loop. It errors with the following Get-EventLog : Invalid value '.\fake.txt' for parameter 'machineName'. at the ELSE line.
param([switch] $FileList)
if (($FileList -eq $true)) { "No file list input" }
Else { "Run on single server"}
2nd code example
param(
[switch]$FileList,
[string]$server)
if ($FileList -eq $true) {
$list = GC $FileList
foreach ($server in $list){Get-EventLog -ComputerName $server -LogName system | Where-Object {$_.EventID -eq '6005'} | Select TimeGenerated,Message | Select -first 1}#End foreach
}
Else{
Get-EventLog -ComputerName $server -LogName system | Where-Object {$_.EventID -eq '6005'} | Select TimeGenerated,Message | Select -first 1
}
If you're set on using the switch parameter this won't help but if you just want a single parameter that can be a file or server name it will.
param (
[parameter(Mandatory = $true)] [string]$FileList
)
if (Test-Path $FileList) {
"File found, do file related commands."
}
Else { "Single server actions." }