Powershell is reanimating variables in Scripts - powershell

Good evening
I have this Problem with this Version
PS C:\temp> $PSVersionTable.PSVersion.Major
4
This is a really strange problem...
despite of initialized variables, PowerShell script is somehow able to reuse variable values from previous invocations.
The script is simple; to show the problem, I work with a list of virtual machines:
Read all Virtual Machines into an Array
Select the 1st Element in the Array
Add a new Property to the Object from step 2
The Problem: if I run the script a second time, the new Property is already there - despite all Variables are initialized. If I start the Script in a new session, the new Property is missing on the 1st run, afterwards it is already there.
Here is the simple Code:
Set-StrictMode -Version 2.0
# Read all Virtual Machines into an Array
$AllVMs = #()
$AllVMs = Get-VM
# Get the 1st Virtual Machine
$VM = $null
$VM = $AllVMs[0]
# Prepare my Property
$MyList = #()
$MyList += "Test"
# If the Property already exists, just add my List
if ($VM.PSobject.Properties.Name -match "MyList") {
$VM.MyList += $MyList
} else {
# My Property does not exist: create it
$VM | Add-Member –MemberType NoteProperty –Name MyList –Value ($MyList)
}
# Give Back my VM Object
$VM
To test the script, I just count the number of MyList-Elements:
PS C:\temp> $result = c:\temp\testvar.ps1
PS C:\temp> $result.MyList.Count
1
PS C:\temp> $result = c:\temp\testvar.ps1
PS C:\temp> $result.MyList.Count
2
…
Does somone can help me with this Problem?
Thanks a lot for any help!!
Kind regards,
Tom

I have asked this question to 'the scripting guy' Forum, too.
I've got two great answers:
From jrv:
You are not exiting from PowerShell. The VM object is dynamic to the session. It persists until you close PowerShell. Some objects are like this. The code base drags them I to PowerShell and they remain cached. I suspect this is what is happening here.
From Evgenij Smirnov:
Hi,
this appears to be specific to the VM object. If I substitute Get-VM by Get-Process or Get-ChildItem c:\ I do not experience this behaviour. If I select a new VM every time I run the script, it does not retain the property. On the other hand, if I do (Get-VM)[0].MyList after running the script four times, I get four entries.
So this persistence is obviously built into the Hyper-V module, the custom property getting added to the instance of the VM object itself. So you could initialize MyTest to empty on the whole VM collection like this:
$AllVMs | foreach {
if ($_.PSobject.Properties.Name -match "MyList") {
$_.MyList = #()
}
}
Kind regards, Tom

Related

Using Powershell to find last step run from an SQL agent job

I have a powershell script that provides SQL Agent Job information. The script is meant to monitor the jobs and tell me if they failed the last time they ran. However i also want to know which was the last run step. and i can't seem to figure out how.
I am querying the Sql server mangement object, because it needs to be usable on multiple remote servers (remote connections) and i wish to avoid running SQL scripts.
Keep in mind i'm rather new to powershell.
This is the code i have so far: And i have loaded the SMO libraries, i just didn't show it the copied script.
## Get Jobstep Class SMO
Push-Location; Import-Module SQLPS -DisableNameChecking; Pop-Location;
$JobStep = New-Object microsoft.sqlserver.management.smo.agent.jobstep
## Run the select from the local SQL server management objects
$SQLSvr = "."
$MySQLObject = new-object Microsoft.SqlServer.Management.Smo.Server `
$SQLSvr;;
$Select = ($MySQLObject.JobServer.jobs) `
| Select Name, isEnabled, `
lastRunDate, lastRunOutCome, NextRunDate `
| Where-Object {$_.LastRunDate -ge ((Get-Date).adddays(-2)) } `
| ft -AutoSize;
$Select
The push-location section is meant to get the correct smo class for selecting the job step (unsure if it is right), however i haven't added anything from it.
Now the script does work, i get no errors and i get returned the overall information i want, but i cannot figure out how to add the jobstep - and i have consulted google. I'm well aware that i need to add more to the select, but what is the issue for me. So, how do i extract the last run job step from SQL Agent job, using the SMO, and add it to the above script?
You can use the SqlServer module from the PowerShell Gallery (Install-Module SqlServer), and then something like:
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName jobname
$h[0] will give you the last step ran.
This will give you the result in the format you wanted:
Get-SqlAgentJob -ServerInstance servername |
Where-Object {$_.LastRunDate -ge ((Get-Date).AddDays(-2))} | ForEach-Object {
$h = Get-SqlAgentJobHistory -ServerInstance servername -JobName $_.Name
[PSCustomObject]#{
Name = $_.Name
IsEnabled = $_.IsEnabled
LastRunDate = $_.LastRunDate
LastRunOutcome = $_.LastRunOutcome
NextRunDate = $_.NextRunDate
LastRunStep = $h[0].StepName
}
}

Get Username from computer name using powershell and sccm

I'm attempting to do the following :
Query SCCM using Powershell for a computer name, then, within the output of that, get the UserName.
Right now, I am using :
Get-CMDevice -name <computername>
This returns the entire record. Within that is an item called "UserName". That's what I want to extract out.
It's been a very long time since working with powershell, let alone the SCCM plugins.
Either use the member reference operator (.):
(Get-CMDevice -Name AComputerName).UserName
or use Select-Object -ExpandProperty:
Get-CMDevice -Name AComputerName |Select-Object -ExpandProperty UserName
Afternoon,
You should just be able to put the command in brackets and select the property directly as shown below:
(Get-CMDevice -name <computername>).UserName

I cannot redirect or hide Get-VM output

So I have a line of code to retrieve the VMID from a hyper-v vm. Running on Server 2016 so it should be at least Powershell 5.0.
[string]$vmid = (Get-VM $VMName).VMID
Pulls out the ID just how I need. The problem is the script also prints out the full result of Get-VM. I cannot for the life of me figure out how to mute or redirect it.
[void] doesn't work cause I'm pulling it into a string.
*> $null, 2> $null, 1> $null all do nothing, and I've tried both in the parenthesis and after the expression.
Piping to Out-Null has no effect either in those locations.
Any idea how I'm supposed to hide this? I really don't need all this info.
On my hyper-v server I had to run
$vmid = (Get-VM $VMName).VMid.Guid
to save only the string to variable
The above command works for me, so it's probably somewhere else in your code that you are getting the unwanted output?
So instead of the above, I ended up defining a $vm variable when creating the VM, then using it in the operations below.
$vm = New-VM ...
...
[string]$vmid = $vm.VMID.GUID

Execute a different command depending on the output of the previous

I am trying out something which is quite simple, yet I can't find an answer or rather can't figure out how to ask the question on Google.
So I thought it would be better to just show what I'm doing with pictures, here.
Here is the script I'm working on:
What it does is simple: get all virtual machines depending on their state (running, saved or off) and then starting them or stopping them. That is where I am having trouble.
I tried to pipe it with different commands, but it keeps giving an error which is
The input object cannot be bound to any parameters for the command either
because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
So what I want is if the machine are running then save them. Is there a way to do so?
Use a ForEach-Object loop and a switch statement:
Get-VM -VMName $name | ForEach-Object {
switch ($_.State) {
'running' {
# do some
}
'saved' {
# do other
}
'off' {
# do something else
}
default {
throw ('Unrecognized state: {0}' -f $_.State)
}
}
}
I think the actual issue here (shown by the error message) is that start-vm doesn't accept pipeline input. I'm guessing this is the Hyper-V Start-VM cmdlet, by the way.
You could do this to get around the lack of pipeline-aware parameters:
Get-VM -VMName $name | where {$_.State -eq $state} | foreach-object {Start-VM -VM $_}

Not Seeing Expected Output When Capturing Results of Command to Variable and Writing to Console in Powershell 2.0

I'm trying to understand what's going on here in Powershell (v2.0, if important). I'm capturing the results of a command to a variable and when I write it to the console, I'm not getting the results I expect. Everything but the output is functioning as expected.
This is an MCVE that acts in the same way as a script that I wrote. I've just broken it down so that I can provide commentary on what's happening, where it's not working the way I think it should work, and what I think may be happening.
In this first snippet, I'm validating the status of the service MyService
on computer svr0123. This gives the output that I'm expecting.
PS C:\Temp> Get-Service -Name MyService -CN svr0123
Status Name DisplayName
------ ---- -----------
Stopped MyService My_Service_Display_Name
PS C:\Temp>
In this second snippet, I'm doing the same, only assigning the output to
$results with the intention of restarting any stopped services. Again, this
gives the output I'm expecting.
PS C:\Temp> $results = Get-Service -Name MyService -CN svr0123
PS C:\Temp> Write-Output $results
Status Name DisplayName
------ ---- -----------
Stopped MyService My_Service_Display_Name
PS C:\Temp>
Finally, I'm restarting the service, then writing the contents of $results
to the console. This does not function as expected. I would anticipate
that the contents of $results would be the same as the previous two outputs,
but instead I get:
PS C:\Temp> $results | Where { $_.Status -eq "Stopped" } | Set-Service -Status Running
PS C:\Temp> Write-Output $results
Status Name DisplayName
------ ---- -----------
Running MyService My_Service_Display_Name
PS C:\Temp>
This is incorrect unless each time I reference the contents of
$results it is calling the Get-Service command again, which is counterintuitive. If that's the case, it
appears that I'm not storing the output of the command, but rather I'm
storing an alias to the command. If I write the contents of $results to the console before doing the restart, everything outputs as expected.
This is a trivial fix, but I'm trying to understand the "Why" behind what I'm observing. My questions are:
Is this, in fact, what is occurring? Where in the Powershell documentation
can I learn more about this?
If this is what is occurring, is there a way that I can just store the
output so that I'm not incurring multiple calls? It's trivial in this
case, but my script will be used on a busy network and may at times have
to query hundreds of servers in a given run.
When you call Get-Service -Name MyService -CN svr0123 it is returning a ServiceController object, not just the text of the output.
So, when you call this line:
$results = Get-Service -Name MyService -CN svr0123
$results is not just a string variable containing the output of the command. It is actually a variable of type ServiceContoller.
Run this to see:
$results.GetType()
You will get this:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ServiceController System.ComponentModel.Component
So, the second time you execute Write-Output $results, the service has been started and the $results variable is displaying the current status of the service.
I think it does not re-call Get-Service when you display the service object. I think the $service object has an internal state which is a text value, and when you pipe it to Set-Service that state value gets changed as well as the service being started/stopped.
Since trying to change it directly with $service.Status = "Running" generates an error because the property is read-only, this change could be happening through the service object's own $service.Start() and $service.Stop() methods found from $service | get-member
Supporting evidence from some quick tests:
I $service = get-service testsvc; $service and see the state is Running, then I go to Control Panel and stop the service there, and the $service state does not change.
I call $service.Start() directly to restart the service, and I get an exception (possibly because my PowerShell is not running as an Admin) so the service does not actually start running, however $service.Status does change (incorrectly) to say the service is running.
This way I get a disconnect between the reported status and the actual status is convincing me, and the way it seems implausible/impractical for every object to "know" how it was generated and arbitrarily re-run that code (what if it was a 30 minute query to generate it?) but I don't know for sure what the interactions are.