How to find datastore clusters presented to compute cluster? - powercli

I am using PowerCLI script calls within a rest service. The rest service is being passed a bunch of data about new VM build requests. For deployment, the only data (relevant to this question) I am getting is the vcenter server and the compute cluster name. I need to figure out a way to determine what datastore cluster to deploy the VM on by using the compute cluster name. If I do a get-cluster $clustername | fl * I don't see any correlation between the computer cluster and the datastore cluster.
If I get info on a VM host (get-vmhost $vmhost) within a cluster, I do see the datastoreIDlist as a property of the VMhost object and can see the datastores (not datastoreclusters) that are presented to it. That's not ideal, but could be doable.
I know there is a relationship between compute clusters and storage that is presented to them within vcenter. Why can't I find that in powercli?

Here's one way to see those relationships
get-view -viewtype clustercomputeresource | % {$_.name + " `n " + $_.datastore + "`n"}
Not pretty, but this will list the datastore clusters available to each compute cluster:
$ccr = get-view -viewtype ClusterComputeResource
$pods = get-view -viewType StoragePod
$pods | % {set-variable -name $_.name.replace("-","") -value $_.childentity.value} #my storage cluster names happen to contain hyphens, which do not play nice with get/set-variable, hence the .replace
foreach ($c in $ccr) {
$list = #()
foreach ($vol in $c.datastore.value) { $match = $null
$match = $pods | where {$_.childentity.value -contains $vol} | select -expand name
if ($match) {$list += $match}
} #close foreach vol
set-variable -name $c.name -value $list
} #close foreach c
$ccr.name | % {$_ + ": " + ((get-variable $_).value | select -unique) }

Related

VM power state not updated and returned correctly

I'm trying to power on VMs which I previously powered off using PowerCLI.
When I try to run the following script (part of a bigger one) I still get a status which is not "PoweredOn", even though I can see on the VSphere console that the machine was powered on.
I get this also in other situations and I try to re-get the virtual machines, but I fail to make this work.
If I don't re-get the VMs, I sometimes get error claiming the VM I'm referring to is null.
What am I doing wrong? What am I missing?
Here are the script lines:
$VMs = get-vm | Where-object {($_.Name -like $vmNamePatternToSearch)}# | Out-Null
foreach ($vm in $VMs) {
#$vm = Get-VM -Name $vm.Name #| Out-Null
if ($vm.powerstate -ieq "poweredoff") {
Start-VM -VM $vm -Confirm:$False | Out-Null
Write-Host -NoNewline 'Powering On' $vm.Name.ToString().PadRight(22)
do {
Start-Sleep -Seconds 1
Write-Host -NoNewline '|' $vm.powerstate
} until ($vm.powerstate -ieq "PoweredOn")
Write-Host
}
}
So my output is "| PoweredOff| PoweredOff| PoweredOff| PoweredOff| PoweredOff|..."
Even though the machine is already up.
Even if I un-comment the "#$vm = Get-VM -Name $vm.Name #| Out-Null" line - still no go.
I would appreciate your input.
Thanks!
PowerShell's objects are point in time references. So your vm variable will continue to reflect the status of the VM at the time you ran the get-vm cmdlet.
To help overcome this you could run something like the following to reference the updated state of the VM during your loop:
do {
Start-Sleep -Seconds 1
Write-Host -NoNewline '|' $vm.powerstate
} until ((Get-VM $vm).powerstate -ieq "PoweredOn")

Powershell Help to compare

Hope you all are safe and well !!
I am running a script that gives me Azure AD apps with its secret end date property. The property name which gives me all details in Azure AD is “PasswordCredentials” and I am using get-azureadapplication cmdlet.
What is the best way to check If the app has end date value within a month and filter on it, I tried where-object with get-date.adddays(30) and tried to compare with -lt operators.
Appreciate your support here.
Well, in your case, you should note there may be several PasswordCredentials for one app, so you need to use a loop to check every PasswordCredential.
And the EndDate you got from the Get-AzureADApplication command is a UTC time, but Get-Date returns the local time, so you need to use (Get-Date).AddDays(30).ToUniversalTime() instead of (Get-Date).AddDays(30). Also, if you just want to check the expiring ones, you must exclude the ones that have expired, so use $PasswordCredential.EndDate -gt $nowdate like below.
As you did not give your script, I can just give a sample for you, my sample is for a single app, if you want to check all the apps in your tenant, use a loop to do that.
$PasswordCredentials = (Get-AzureADApplication -ObjectId <object-id>).PasswordCredentials
$nowdate = (Get-Date).ToUniversalTime()
$wantdate = (Get-Date).AddDays(30).ToUniversalTime()
foreach($PasswordCredential in $PasswordCredentials){
if($PasswordCredential.EndDate -lt $wantdate -and $PasswordCredential.EndDate -gt $nowdate){
$keyid = $PasswordCredential.KeyId
Write-Output "The key with KeyId $keyid will expire"
}
}
Update:
If you want to get the AppId and AppName, you could use the script below.
$apps = Get-AzureADApplication -All $true
$nowdate = (Get-Date).ToUniversalTime()
$wantdate = (Get-Date).AddDays(30).ToUniversalTime()
foreach($app in $apps){
$PasswordCredentials = $app.PasswordCredentials
$appid = $app.AppId
$displayname = $app.DisplayName
foreach($PasswordCredential in $PasswordCredentials){
if($PasswordCredential.EndDate -lt $wantdate -and $PasswordCredential.EndDate -gt $nowdate){
$a = $app | select #{Name="AppId"; Expression={$appid}}, #{Name="DisplayName"; Expression={$displayname}}, #{Name="EndDate"; Expression={$PasswordCredential.EndDate}}
Write-Output $a
}
}
}

Starting Apache fails without error

I have an Apache service 'SERV[XYZ]' in my Windows services and I would like to be able to start it from a PowerShell script.
In the script, I added variable holding service name:
$serv = "SERV[XYZ]";
stop-service $serv;
start-service $serv;
but this does not start the service. PowerShell executes without error.
The *-Service cmdlets do wildcard matches on the service name. See for instance the documentation of Start-Service:
Parameters
-DisplayName<String[]>
Specifies the display names of the services to be started. Wildcards are permitted.
The pattern [XYZ] means "match any of the characters X, Y, or Z" (like in regular expressions), so your statements try to stop/start services named SERVX, SERVY and/or SERVZ. To match a literal string [XYZ] you need to prevent the square brackets from being treated as special characters, e.g. like this:
$serv = 'SERV[[]XYZ[]]'
Stop-Service $serv
Start-Service $serv
If you have only one service whose name starts with SERV you could also use a pattern like SERV*, or perhaps SERV?XYZ? where the ? (wildcard matching a single character) mask the square brackets.
Another option would be to use Get-Service without a name and filter the results via Where-Object:
$serv = 'SERV[XYZ]'
Get-Service | Where-Object { $_.Name -eq $serv } | Stop-Service
Get-Service | Where-Object { $_.Name -eq $serv } | Start-Service

How to search with multiple keys in a hash table in powershell

I'm writing a Powershell script and in it I'm using a hash table to store information about database checks. The table has 5 keys (host, check, last execution time, last rep, status) and I want to search in my table for values where:
$s = $table where $host -eq $hostname -and check -eq $check
Does anyone have any idea how this is done? And if it makes any difference, the script cannot rely on .NET framework higher than 2.0
I´m new to Powershell and scripting in general so this might be very obvious but I still can't seem to find an answer on Google. Also if someone knows a good reference page for Powershell scripting I would really appreciate a link.
Gísli
EDIT: Don't see how it matters but here is a function I use to create a hash table:
function read_saved_state{
$state = #{}
$logpos = #{}
$last_log_rotate = 0
foreach($s in Get-Content $saved_state_file){
$x = $s.split('|')
if($x[0] -eq 'check'){
$state.host = $x[1]
$state.check = $x[2]
$state.lastexec = $x[3]
$state.lastrep = $x[4]
$state.status = $x[5]
}
elseif($x[0] -eq 'lastrotate'){
$last_log_rotate = $x[1]
}
elseif($x[0] -eq 'log'){
$logpos.lastpos = $x[3]
}
}
$saved_state_file has one line for each check run and can also have a line for last log rotate and last log position. There can be as many as 12 checks for one host.
I'm trying to extract a particular check, run at a particular host, and changing the lastexec_time, last_rep and status.
return $state,$logpos,$last_log_rotate
}
Assuming you have an array or list of hashtables (not entirely clear from the question), your syntax is pretty close:
$s = $tables | where {($_.host -eq $hostname) -and ($_.check -eq $check)}

In PowerShell, what's the best way to join two tables into one?

I'm fairly new to PowerShell, and am wondering if someone knows of any better way to accomplish the following example problem.
I have an array of mappings from IP address to host-name. This represents a list of active DHCP leases:
PS H:\> $leases
IP Name
-- ----
192.168.1.1 Apple
192.168.1.2 Pear
192.168.1.3 Banana
192.168.1.99 FishyPC
I have another array of mappings from MAC address to IP address. This represents a list of IP reservations:
PS H:\> $reservations
IP MAC
-- ---
192.168.1.1 001D606839C2
192.168.1.2 00E018782BE1
192.168.1.3 0022192AF09C
192.168.1.4 0013D4352A0D
For convenience, I was able to produce a third array of mappings from MAC address to IP address and host name using the following code. The idea is that $reservations should get a third field, "Name", which is populated whenever there's a matching "IP" field:
$reservations = $reservations | foreach {
$res = $_
$match = $leases | where {$_.IP -eq $res.IP} | select -unique
if ($match -ne $NULL) {
"" | select #{n="IP";e={$res.IP}}, #{n="MAC";e={$res.MAC}}, #{n="Name";e={$match.Name}}
}
}
The desired output is something like this:
PS H:\> $ideal
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.4 0013D4352A0D
Is there any better way of doing this?
After 1.5 years, the cmdlet I had pasted in the original answer has undergone so many updates that it has become completely outdated. Therefore I have replaced the code and the ReadMe with a link to the latest version.
Join-Object
Combines two object lists based on a related property between them.
Description
Combines properties from one or more objects. It creates a set that can be saved as a new object or used as it is. An object join is a means for combining properties from one (self-join) or more object lists by using values common to each.
Main features
Intuitive (SQL like) syntax
Smart property merging
Predefined join commands for updating, merging and specific join types
Well defined pipeline for the (left) input objects and output objects (preserves memory when correctly used)
Performs about 40% faster than Compare-Object on large object lists
Supports (custom) objects, data tables and dictionaries (e.g. hash tables) for input
Smart properties and calculated property expressions
Custom relation expressions
Easy installation (dot-sourcing)
Supports PowerShell for Windows (5.1) and PowerShell Core
The Join-Object cmdlet reveals the following proxy commands with their own (-JoinType and -Property) defaults:
InnerJoin-Object (Alias InnerJoin or Join), combines the related objects
LeftJoin-Object (Alias LeftJoin), combines the related objects and adds the rest of the left objects
RightJoin-Object (Alias RightJoin), combines the related objects and adds the rest of the right objects
FullJoin-Object (Alias FullJoin), combines the related objects and adds the rest of the left and right objects
CrossJoin-Object (Alias CrossJoin), combines each left object with each right object
Update-Object (Alias Update), updates the left object with the related right object
Merge-Object (Alias Merge), updates the left object with the related right object and adds the rest of the new (unrelated) right objects
ReadMe
The full ReadMe (and source code) is available from GitHub: https://github.com/iRon7/Join-Object
Installation
There are two versions of this Join-Object cmdlet (both versions supply the same functionality):
Join Module
Install-Module -Name JoinModule
Join Script
Install-Script -Name Join
(or rename the Join.psm1 module to a Join.ps1 script file)
and invoked the script by dot sourcing:
. .\Join.ps1
Answer
To answer the actual example in the question:
$reservations |LeftJoin $leases -On IP
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.4 0013D4352A0D
Performance
A little word on performance measuring:
The PowerShell pipeline is designed to stream objects (which safes memory), meaning that both¹ lists of input objects usually aren't (shouldn't be) resident in memory. Normally they are retrieved from somewhere else (i.e. a remote server or a disk). Also, the output usually matters where linq solutions are fast but might easily put you on the wrong foot in drawing conclusions because linq literally defers the execution (lazy evaluation), see also: fastest way to get a uniquely index item from the property of an array.
In other words, if it comes to (measuring) performance in PowerShell, it is important to look to the complete end-to-end solution, which is more likely to look something like:
import-csv .\reservations.csv |LeftJoin (import-csv .\leases.csv) -On IP |Export-Csv .\results.csv
(1) Note: unfortunately, there is no easy way to build two parallel input streams (see: #15206 Deferred input pipelines)
(more) Examples
More examples can be found in the related Stack Overflow questions at:
Combining Multiple CSV Files
Combine two CSVs - Add CSV as another Column
CMD or Powershell command to combine (merge) corresponding lines from two files
Can I use SQL commands (such as join) on objects in powershell, without any SQL server/database involved?
Powershell match properties and then selectively combine objects to create a third
Compare Two CSVs, match the columns on 2 or more Columns, export specific columns from both csvs with powershell
Merge two CSV files while adding new and overwriting existing entries
Merging two CSVs and then re-ordering columns on output
Efficiently merge large object datasets having multiple matching keys
Is there a PowerShell equivalent of paste (i.e., horizontal file concatenation)?
How to compare two CSV files and output the rows that are in either of the file but not in both
How to join two CSV files in Powershell with SQL LIKE syntax
How can merge 3 cycle and export in one table
Merging two arrays object into one array object in powershell
And in the Join-Object test script.
Please give a 👍 if you support the proposal to Add a Join-Object cmdlet to the standard PowerShell equipment (#14994)
This can also be done using my module Join-Object
Install-Module 'Join-Object'
Join-Object -Left $leases -Right $reservations -LeftJoinProperty 'IP' -RightJoinProperty 'IP'
Regarding performance, I tested against a sample data of 100k lines:
Hashtable example posted by #js2010 run in 8 seconds.
Join-Object by me run in 14 seconds.
LeftJoin by #iRon run in 1 minute and 50 seconds
Here's a simple example using a hashtable. With big arrays, this turns out to be faster.
$leases =
'IP,Name
192.168.1.1,Apple
192.168.1.2,Pear
192.168.1.3,Banana
192.168.1.99,FishyPC' | convertfrom-csv
$reservations =
'IP,MAC
192.168.1.1,001D606839C2
192.168.1.2,00E018782BE1
192.168.1.3,0022192AF09C
192.168.1.4,0013D4352A0D' | convertfrom-csv
$hashRes=#{}
foreach ($resRecord in $reservations) {
$hashRes[$resRecord.IP] = $resRecord
}
$leases | foreach {
$other = $hashRes[$_.IP]
[pscustomobject]#{IP=$_.IP
MAC=$other.MAC
Name=$_.name}
}
IP MAC Name
-- --- ----
192.168.1.1 001D606839C2 Apple
192.168.1.2 00E018782BE1 Pear
192.168.1.3 0022192AF09C Banana
192.168.1.99 FishyPC
Easiest way I've found to Merge two Powershell Objects is using ConvertTo-Json and ConvertFrom-Json
One liner based on the OPs Senario:
$leases | foreach {(ConvertTo-Json $_) -replace ("}$", (ConvertTo-Json ($reservations | where IP -eq $_.IP | select * -ExcludeProperty IP)) -Replace "^{", ",")}
| ConvertFrom-Json
Results in:
IP Name Mac
-- ---- ---
192.168.1.1 Apple 001D606839C2
192.168.1.2 Pear 00E018782BE1
For another example lets make a couple objects:
$object1 = [PSCustomObject]#{"A" = "1"; "B" = "2"}
$object2 = [PSCustomObject]#{"C" = "3"; "D" = "4"}
Merge them together using Json by replacing the opening and closing brackets:
(ConvertTo-Json $object1) -replace ("}$", $((ConvertTo-Json $object2) -Replace "^{", ",")) | ConvertFrom-Json
Output:
A B C D
- - - -
1 2 3 4
Another example using a group of objects:
$mergedObjects = [PSCustomObject]#{"Object1" = $Object1; "Object2" = $Object2}
Object1 Object2
------- -------
#{A=1; B=2} #{C=3; D=4}
Can just do the same again within a foreach:
$mergedObjects | foreach {(ConvertTo-Json $_.Object1) -replace ("}$", $((ConvertTo-Json $_.Object2) -Replace "^{", ",")) | ConvertFrom-Json}
Output:
A B C D
- - - -
1 2 3 4
You can use script block like this
$leases | select IP, NAME, #{N='MAC';E={$tmp=$_.IP;($reservations| ? IP -eq $tmp).MAC}}