Start-Job PowerShell two jobs getting created - powershell

I want to execute set of un-contained stored procedures, as jobs, to see if there are any issues in the code after making the database as contained database. After some time, I want to see whether the stored procedures are successful or not. But, I see that instead of one job for a stored procedure, two jobs are getting created. How to avoid the creation of two jobs?
$unContainedSProcs = Import-Csv -Path C:\DFLog\UnContained_Parameterless_SPs.csv
$batchSize = 50
$currentCompletedIdx = 0
$jobIds = #()
$scriptToExecute = {
Param($storedProcToExecute)
Invoke-Sqlcmd -Query "Exec $storedProcToExecute" -ServerInstance "<<ServerInstance>>" -Database "<<Database>>" -QueryTimeout 120
}
while ($currentCompletedIdx -le $unContainedSProcs.Length) {
for ($i= 0; $i -le $batchSize; $i++) {
$job = Start-Job -Name $unContainedSProcs[$i].UnContainedSProcName -ScriptBlock $scriptToExecute -ArgumentList $unContainedSProcs[$i].UnContainedSProcName
$jobIds += $job.Id
++$currentCompletedIdx
}
}
When I see the jobs list, using Get-Job, I see two jobs:
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+
| Id | Name | PSJobTypeName | State | HasMoreData | Location | Command |
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+
| 1 | dbo.SPName | BackgroundJob | Completed | True | localhost | param($storedProcToExe... |
| 41 | Job41 | BackgroundJob | Completed | True | localhost | param($storedProcToExe... |
+----+--------------+------------------+-----------+---------------+--------------+------------------------------+

I have figured it out. It is due to wrong definition of for loop condition. When the number of stored procedures is 1 and for loop is running twice: 0, 1. It is due to -le (less than or equal to) Length of $unContainedSProcs(1).
There is only one entry [0] in $unContainedSProcs. For the first run, it is fine. For second run, there is no entry[1] in $unContainedSProcs. So, empty job is getting created. After changing -le to -lt, there is only one job created.
From:
for ($i= 0; $i -le $batchSize; $i++) {
To:
for ($i= 0; $i -lt $batchSize; $i++) {

Related

Input validation/data filtering with Powershell

I've been working on this for a little while, basically all I'm doing is grabbing the licensing from our clients' Office 365 tenant and displaying it in a more readable manner since Office 365 Powershell doesn't output subscription names in a common name. This works perfectly when every subscription is in my CSV of subscription names, however on occasions where the SKU is not in my list (brand new or legacy offerings) the licensing table doesn't populate correctly because it can't find the friendlyname in my CSV (licensing quantities don't match the subscription name because of a blank record when it failed to find the SKU).
What I'm trying to have it do is display the skupartnumber in place of the friendlyname in the event that the subscription is not in my CSV instead of breaking the output. The first snippet below is my current working script that only works if the SKU is in my CSV, the one below it is the my best attempt at trying some input validation but I just can't get it to work. Everything displays correctly except the Subscription column which is blank (I also notice that it takes about 5x as long to run as normal), I would greatly appreciate any assistance offered; thanks!
Works as long as subscription is in my CSV:
$sku = Get-MsolAccountSku | select-object skupartnumber,ActiveUnits,suspendedUnits,ConsumedUnits | sort-object -property skupartnumber
$skudata = import-csv -Header friendlyname,skupartnumber "C:\PShell\cspcatalogalphabet.csv" | where-object {$sku.skupartnumber -eq $_.skupartnumber} | sort-object -property skupartnumber
$result = for ($n = 0; $n -lt #($skudata).Count; $n++) {
[PsCustomObject]#{
Subscription = #($skudata.friendlyname)[$n]
Active = $sku.ActiveUnits[$n]
Suspended = $sku.SuspendedUnits[$n]
Assigned = $sku.ConsumedUnits[$n]
}
}
$result | Format-Table -AutoSize
# Output:
Subscription Active Suspended Assigned
------------ ------ --------- --------
Microsoft Flow Free 10000 0 1
Power Bi (Free) 1000000 0 1
Microsoft Teams Exploratory 100 0 6
My best attempt at input validation which results in no data being read into $skulist.Subscription:
$sku = Get-MsolAccountSku | select-object skupartnumber,ActiveUnits,suspendedUnits,ConsumedUnits
$skulist = import-csv -Header friendlyname,skupartnumber "C:\PShell\cspcatalogalphabet.csv"
$skuname = for ($c = 0; $c -lt #($sku).count; $c++) {
if ($sku.skupartnumber[$c] -in $skulist.skupartnumber) {
[PsCustomObject]#{
Subscription = $skulist.friendlyname | where-object {$sku.skupartnumber[$c] -eq $skulist.skupartnumber}
}
}
else {
[PSCustomObject]#{
Subscription = #($sku.skupartnumber)[$c]
}
}
}
$table = for ($n = 0; $n -lt #($sku).Count; $n++) {
[PsCustomObject]#{
Subscription = #($skuname.Subscription)[$n]
Active = $sku.ActiveUnits[$n]
Suspended = $sku.SuspendedUnits[$n]
Assigned = $sku.ConsumedUnits[$n]
}
}
$table | format-table -AutoSize
# Output:
Subscription Active Suspended Assigned
------------ ------ --------- --------
10000 0 1
1000000 0 1
100 0 6
# An example of data I am grabbing from our clients' accounts:
$sku = Get-MsolAccountSku | select-object skupartnumber,ActiveUnits,suspendedUnits,ConsumedUnits
$sku
# Output:
SkuPartNumber ActiveUnits SuspendedUnits ConsumedUnits
------------- ----------- -------------- -------------
FLOW_FREE 10000 0 1
POWER_BI_STANDARD 1000000 0 1
TEAMS_EXPLORATORY 100 0 6
Instead of relying on the two arrays being aligned - that is, index $n in $sku must correspond to the item at index $n in $skulist for you code to work - you'll want to be able to resolve a value in $skulist based on the actual $SKU.SkuPartNumber value instead.
So how does one do that?!
Feed your $skulist into a [hashtable] instead:
$skulist = #{}
Import-Csv -Header friendlyname,skupartnumber "C:\PShell\cspcatalogalphabet.csv" |ForEach-Object {
$skulist[$_.skupartnumber] = $_.friendlyname
}
And then iterate over $skudata like this (notice there's no need for a for(;;) loop anymore, we don't need to care about array alignment!):
foreach($skuEntry in $sku){
[pscustomobject]#{
Subscription = if($skuList.ContainsKey($skuEntry.SKUPartNumber){$skuList[$skuEntry.SKUPartNumber]}else{$skuEntry.SKUPartNumber})
Active = $skuEntry.ActiveUnits
Suspended = $skuEntry.SuspendedUnits
Assigned = $skuEntry.ConsumedUnits
}
}

Merge two PSCustomObjects into one- PowerShell

I need help in PowerShell to combine two outputs or two PSCustomObjects into One.
For example,
$services = Get-Service | Select Name, Starttype,Status
$processes = Get-Process | Select ID
I need the output with the table headers
Name, Starttype, Status, ID
I have already tried creating CSV and joining them but the problem is Process ID starts when the entire output ends for the services. I need them to a parallel.
Second I have tried to create PSCustomObjects but no luck.
Please help me with the PowerShell code.
Actual code that I'm trying to achieve.
**$exclusionItems = #()
$OasHighItems = #()
foreach($item in $items){
$exclusionItems += [PSCustomObject]#{
EXCLUSION_BY_NAME_OR_LOCATION = $item.EXCLUSION_BY_NAME_OR_LOCATION
EXCLUSION_EXCLUDE_SUBFOLDERS = $item.EXCLUSION_EXCLUDE_SUBFOLDERS
EXCLUSION_ON_READ= $item.EXCLUSION_ON_READ
}
}
foreach($oas in $oashigh){
$oashighItems += [PSCustomObject]#{
OAS_PROCESSES_LIST = $oas
}
}
$Array = #()
$Array = $exclusionItems,$oashighItems
$Array | Update-FirstObjectProperties | Export-Excel $ExcelParams -TableName Table -Show**
I'm assuming you want to join the two objects by their names, i.e. match the Process-Name with the Service-Name. For this you can loop over all processes & services, keep only those where service-name equals process-name, and use a calculated property to merge the result into one object:
$services = Get-Service;
Get-Process | ForEach-Object {$p = $_; $services |
Where-Object{$p.ProcessName -eq $_.Name} |
Select-Object Name,StartType,Status,#{n='ID';e={$p.ID}}}
The output on my machine is:
Name StartType Status ID
---- --------- ------ --
CcmExec Automatic Running 14856
CmRcService Automatic Running 5748
FusionInventory-Agent Automatic Running 5996
IBMPMSVC Automatic Running 3540
IntelAudioService Automatic Running 6104
... and so on ...

Powershell Get-Random with Constraints

I'm currently using the Get-Random function of Powershell to randomly pull a set number of rows from a csv. I need to create a constraint that says if one id is pulled, find the other ids that match it and pull their value.
Here is what I currently have:
$chosenOnes = Import-CSV C:\Temp\pk2.csv | sort{Get-Random} | Select -first 6
$i = 1
$count = $chosenOnes | Group-Object householdID
foreach ($row in $count)
{
if ($row.count -gt 1)
{
$students = $row.Group.Student
foreach ($student in $students)
{
$name = $student.tostring()
#...do something
$i = $i + 1
}
}
else
{
$name = $row.Group.Student
if($i -le 5)
{
#...do something
}
else
{
#...do something
}
$i = $i + 1
}
}
Example dataset
ID,name
165,Ernest Hemingway
1204,Mark Twain
1578,Stephen King
1634,Charles Dickens
1726,George Orwell
7751,John Doe
7751,Tim Doe
In this example, there are 7 rows but I'm randomly selecting 6 in my code. What needs to happen is when ID=7751 then I must return both rows where ID=7751. The IDs cannot not be statically set in the code.
Use Get-Random directly, with -Count, to extract a given number of random elements from a collection.
$allRows = Import-CSV C:\Temp\pk2.csv
$chosenHouseholdIDs = ($allRows | Get-Random -Count 6).householdID
Then filter all rows by whether their householdID column contains one of the 6 randomly selected rows' householdID values (PSv3+ syntax), using the -in array-containment operator:
$allRows | Where-Object householdID -in $chosenHouseholdIDs
Optional reading: performance considerations:
$allRows | Get-Random -Count 6 is not only conceptually simpler, but also much faster than $allRows | Sort-Object { Get-Random } | Select-Object -First 6
Using the Time-Command function to compare the performance of two approaches, using a 1000-row test file with 10 columns yields the following sample timings on my Windows 10 VM in Windows PowerShell - note that the Sort-Object { Get-Random }-based solution is more than 15(!) times slower:
Factor Secs (100-run avg.) Command TimeSpan
------ ------------------- ------- --------
1.00 0.007 $allRows | Get-Random -Count 6 00:00:00.0072520
15.65 0.113 $allRows | Sort-Object { Get-Random } | Select-Object -First 6 00:00:00.1134909
Similarly, a single pass through all rows to find matching IDs via array-containment operator -in performs much better than looping over the randomly selected IDs and searching all rows for each.
I tried sticking with your beginning and came up with this.
$Array = Import-CSV C:\test\StudtentTest.csv
$Array | Sort{Get-Random} | select -first 2 | %{
$id = $_.id
$Array | ?{$_.id -eq $id} | %{
$_
}
}
$Array will be your parsed CSV
We pipe in and sort by random select -first 2 (in this case)
Save the ID of the object into $id and then search the array for that ID and dispaly each that matches
If same ID does match you end up with something like
ID name
-- ----
7751 John Doe
7751 Tim Doe
1634 Charles Dickens

Parsing multiple valus in multiple variables

I am trying to find a way to execute a command in powershell and put each line of the result in a different variable to use them later in my script. For example if i execute the below command:
C:\> Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
I will get the below:
Name
----
HQ-LinServ-01a
HQ-Win2012-01a
HQ-Web-02a
I want to have a script that will add each line in a different variable in a script (excluding the first which is name).
how can i do that.
Thank you for your help.
You can use Set-Variable in a loop to put each value in a separate variable:
$i = 0
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
Set-Variable -Name "vm$i" -Value $_
$i++
}
However, that usually isn't good advice. It's more common to put all names in one (array) variable:
$vmList = ...| Get-Vm | Select-Object -Expand Name
so you can access individual names via $vmList[<index>], or (if you need access by some kind of name) in a hashtable:
$i = 0
$vmList = #{}
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
$vmList["vm$i"] = $_
$i++
}
Best practice would depend on the particular scenario you need this for, though.
Thank you for your reply,
I have tried you answer but it seems that i am using PowerCLI for VMware it does not include Select-Object -Expand (not sure i had an exception), However your answer have mad me reach to a suitable answer for this.
I have used the below and it worked fine using foreach and adding the values in an array and then reading them as below:
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
However when using this the names was not totally correct as they had some additional characters as below:
vm number 1 is #{Name=HQ-Web-01a}
vm number 2 is #{Name=HQ-LinServ-01a}
vm number 3 is #{Name=HQ-Win2012-01a}
so i used split and trim to get rid of these as below and worked fine.
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
$p1vmlist[$p1] = ($p1vmlist[$p1]) -split("=") | Select-Object -Last 1
$p1vmlist[$p1] = $p1vmlist[$p1].trimend("}")
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
Output:
vm number 1 is HQ-Web-01a .
vm number 2 is HQ-LinServ-01a .
vm number 3 is HQ-Win2012-01a .
Thank you so much for your answer that helped me a lot.
I am really enjoying scripting now.

Powershell Shutdown VM Script

I have a script that can shutdown vms based on names entered into an array.
That part works fine, but the next part after the shutdown initiates is supposed to wait for a period and notify how many vms it is waiting to shut down before it moves on to another phase of shutting down other vms.
I am not getting any vms counted out of the array. Here is the code for a particular phase:
$waittime = 5 #Seconds
#Create Phase 1 Array
[Array] $PHASE1 = "TestSvr2008"
# For each of the VMs on the Hyperv hosts that are powered on
Foreach ($VM in ($PHASE1 | %{ Get-VM | Where { $_.State -eq "Running" }})){
# Shutdown the guest cleanly
$VM | Stop-VM -Confirm:$false }
# Set the amount of time to wait before assuming the remaining powered on guests are stuck
$waittime = 120 #Seconds
$Time = (Get-Date).TimeofDay
do {
# Wait for the VMs to be Shutdown cleanly
sleep 1.0
$timeleft = $waittime - ($Newtime.seconds)
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
Write "Waiting for shutdown of $numvms VMs or until $timeleft seconds"
$Newtime = (Get-Date).TimeofDay - $Time
} until ((#($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count) -eq 0 -or ($Newtime).Seconds -ge $waittime)
Thanks
This line in do and until blocks has issues:
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
$_.$VM is typo, it should be $_.VM
$_.VM -eq $PHASE1 tries to check if VM name is equal to array. Comparisons do not work that way.
ForeEach-Object is unnecessary
Get-Vm accepts array of VM names by pipeline or directly as Name parameter. So you can do this:
$numvms = ($PHASE1 | Get-VM).Count
or
$numvms = (Get-VM -Name $PHASE1).Count