Powershell: Use variable from inner loop as condition of outer loop - powershell

we're using a PS-Script for creating new AD-users and configure everything in one go.
I'd like to create a unique 6-digit number and check if it's already in use.
I thought a do-until-loop will do the trick, but i can't get it to re-run if a match is found.
My code as of now is this:
$AllUsedPins=(Get-ADUser -Properties ExtensionAttribute3 -Filter {ExtensionAttribute3 -like '*'} | select -ExpandProperty ExtensionAttribute3)
do{
$PinIsUnique="True"
#$TempPin="333860"
$TempPin=(Get-Random -Minimum 0 -Maximum 999999).ToString('000000')
foreach ($UsedPin in $AllUsedPins) {
If ("$TempPin" -eq "$UsedPin" ) {
$PinIsUnique="False"
Write-Host '$PinIsUnique(IF): '$PinIsUnique
}
}
Write-Host '$PinIsUnique(inDO): '$PinIsUnique
}
until ($PinIsUnique="True")
Write-Host '$PinIsUnique(outDO): '$PinIsUnique
#Set-ADUser -Identity $sam -Replace #{extensionAttribute3=$TempPin}
$TempPin="333860" is for testing, as it is one already used. The loop should then run endlessly, but stops after one run nevertheless, so I added some Write-Outputs to narrow it down. They show:
$PinIsUnique(IF): False
$PinIsUnique(inDO): False
$PinIsUnique(outDO): True
$TempPin: 333860
What puzzles me here:
$PinIsUnique is only set at the beginning of the do-loop, before that it doesn't even exist.
$PinIsUnique is False until end of the do-loop, but True when used for checking the while-condition?
I wasted by far too much time on this already: Reading up on loops in general, nested loops, global variables, return, Write-Host vs Write-Output and other things that sounded as they could help me solve this...
I altered the code for easier testing as nearly half of all options should trigger at least one re-run:
$AllUsedPins=("01", "02", "03", "04", "05", "11", "12", "13", "14", "15")
do{
$PinIsUnique="True"
$TempPin=(Get-Random -Minimum 0 -Maximum 20).ToString('00')
foreach ($UsedPin in $AllUsedPins) {
If ("$TempPin" -eq "$UsedPin" ) {
$PinIsUnique="False"
}
}
}
until ($PinIsUnique="True")
Write-Host '$AllUsedPins: '$AllUsedPins
Write-Host '$TempPin: '$TempPin
As i realized just now while trying the altered code, $TempPin is useable outside the do-loop just fine... So what am I missing here?
Some litte extra if someone cares:
I tried to stop the inner foreach-loop if an existing pin was found, but couldn't get that to work either. Neither break nor a labeled break worked... at least the way i tried.
Also, as i'm no expert in scripting/programming but can usually identify a well-written piece of code: How would you guys have solved this?
Thanks in advance!

Sometimes you just have to talk about a problem to get to the solution... and to be ashamed of yourself...
Just edited
until ($PinIsUnique="True")
to
until ($PinIsUnique -eq "True")
and it works just fine...
Other examples of how to solve it are encouraged, while i'm standing in the corner facing the wall...

Related

Powershell return value from function driving me crazy

I know, there are hundreds of pages that address the -in my opinion- strange way that Powershell handles return values from functions, and I must have visited about half of them ;=)
This particular one drives me nuts.
Consider the following:
I'm doing a call to a function with one parameter (call is not done from within another function):
$result = getVMinfo($vm)
The function getVMinfo looks like this :
function getVMinfo {
param (
[string]$vm
)
try {
Get-WmiObject -Class Win32_DiskDrive -Computername $vm -ErrorAction Stop
}
catch {
$ErrorReturn = $_.Exception.Message
}
if ($ErrorReturn) {
Write-Host "Error =" $ErrorReturn
return $ErrorReturn
}
}
Looks simple enough, and other functions do work when returning a value in this way.
Now, if I run the script, The write-Host bit in the catch does show me that $ErrorReturn is filled with a string (tested that with $ErrorReturn.GetType() ).
However, $result in the calling statement is always empty.
I have tried many suggestions, like creating an array, and use the .Add() to add the errorstring to the array, and then return the array. Nothing seems to work.
I am really at a loss here. Don't understand what I am doing wrong.
Please, help me !

Comparing AD sites and SCCM boundaries

I m trying to compare the Active directory sites with SCCM boundaries, by using the below powershell scripts, but its not giving the output as expected.
There are 3 AD sites actually available in SCCM, however the script gives me an output that there is no AD sites available in SCCM boundaries.
$sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
$CMBoundary = Get-CMBoundary | select value
foreach ($adsite in $sites.name) {
foreach ($cmb in $CMBoundary.value ) {
if (($cmb | select value) -eq ($adsite | select name)) {
"$adsite available in CM"
}
else { "$adsite is NOT in CM $cmb" }
}
}
Could someone please help me on this.
Think I see the issue.
On line 3, you're selecting the .Name property from $sites and storing it in $adsite, for each step of your loop.
foreach ($adsite in $sites.name) {
However, you then also attempt to take the .Name property again, with this line:
if (($cmb | select value) -eq ($adsite | select name))
This won't work. You've set $adsite to be equal to whatever was in $sites.Name, but that doesn't give a .Name property to $adsite.
Try this again, this time with the second Select statement removed. The reason this was failing is that there wouldn't be anything to compare against. I've revised your code to remove this logic, let me know if it works.
foreach ($adsite in $sites.name) {
foreach ($cmb in $CMBoundary.value ) {
if ($cmb -eq $adsite) {
"$adsite available in CM"
}
else { "$adsite is NOT in CM $cmb" }
}
}
Be warned, however, that if your boundary name doesn't exactly match an AD Site name, that this code will give you a lot of false positives.

PowerShell Switch not working with hyphenation

I'm creating a series of new PSObjects, from a CSV import, and then adding them to $new. I'm using a switch to try and set the value for the "Notes" property, as the object is being created\added, and I've run into something 'hinky'.
When I run this...
$import = Import-Csv c:\somerandom.csv
$new = #()
foreach ($Item in $Import) {
$obj = New-Object PsObject -Property #{
Name = $item.Name
Description = $Item.Description
Quantity = $Item.Quantity
Vendor = $Item.Vendor
SubCategory = "Misc"
Notes = ""
}
switch ($obj.Name) {
"iPod" { $obj.Notes = "Burn with the rest of the Apple garbage"}
"nVidia GTX 780ti" { $obj.Notes = "Steal immediately!" }
default { $obj.Notes= "Sorry man... I have no idea what that is"}
}
$new += $obj
}
... it works as expected. All of the entries from $import, are recreated in $new, with the addition of my "SubCategory" and "Notes" noteproperties (iPod gets burn tag, 780ti slated to be stolen). But when I run with the following as the switch...
switch ($obj.Name) {
'SOFM090-107-01-PF-R' { $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' { $obj.Notes = "Steal immediately!" }
default { $obj.Notes = "Sorry man... I have no idea what that is"}
}
... It sets all the entries to the 'default' setting on the switch. I tried running the switch with a non-hyphenated name for one entry, and a hyphenated entry for the other, and only the hyphenated version was set properly.
The above code is altered from the actual code, but it properly illustrates what I'm trying to do. I need to add a noteproperty that is based off a list of part numbers, and will fill in the "Notes" entry with a tag of my choosing.
I've tried it with single quotes, double quotes, using the -wildcard and replacing the switch hyphens with *'s, and putting the ` character in before the -'s. Nothing seems to be working.
There's nothing wrong with the code, so the problem must lie in the data. I verified that it works fine with a CSV file that has those exact hyphenated values in the "Name" column.
If the switch doesn't work with the hyphenated names, then the values being imported into the Name property don't match what you have in the switch statement. It's a good idea to always post the data you're working with, or a sample of it, because often that's the source of the problem. Even when it isn't, it helps other people understand what you're trying to accomplish and what your code does. Since we don't have the data, I can suggest a few likely possibilities:
You're manually typing the names into the switch statement, and they look like what's in the CSV, but don't actually match, e.g. you're confusing O with 0 because they look the same in the font you're working with. I'd have suspected something like an en-dash instead of a hyphen, but you say you tried replacing the hyphens with wildcards
You have trailing spaces
You're single-quoting the hyphenated names in the CSV file (Import-Csv only understands double quotes; single quotes would be included in the value).
Here are a couple of things you can try to help identify why the data doesn't match (separately, not both together):
Replace switch ($obj.Name) { with switch -regex ($obj.Name) {
Use the following code to show you exactly what PowerShell is seeing in the Name property for each item and which switch conditions are being executed:
Write-Host -NoNewline "[$($obj.Name)] "
switch ($obj.Name) {
'SOFM090-107-01-PF-R' {Write-Host 'burn'; $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' {Write-Host 'steal'; $obj.Notes = "Steal immediately!"}
default {Write-Host 'sorry'; $obj.Notes = "Sorry man... I have no idea what that is"}
}
If you post the data, we'll probably be able to tell you exactly why it's not working. But I can pretty much guarantee you that if you're using that code, the problem is that the imported Name values that aren't being matched with the right Notes values are in some way not the same as what you have in the switch conditions.

How can I quickly find VMs with serial ports in PowerCLI

I have a script that takes about 15 minutes to run, checking various aspects of ~700 VMs. This isn't a problem, but I now want to find devices that have serial ports attached. This is a function I added to check for this:
Function UsbSerialCheck ($vm)
{
$ProbDevices = #()
$devices = $vm.ExtensionData.Config.Hardware.Device
foreach($device in $devices)
{
$devType = $device.GetType().Name
if($devType -eq "VirtualSerialPort")
{
$ProbDevices += $device.DeviceInfo.Label
}
}
$global:USBSerialLookup = [string]::join("/",$ProbDevices)
}
Adding this function adds an hour to the length of time the script runs, which is not acceptable. Is it possible to do this in a more efficient way? All ways I've discovered are variants of this.
Also, I am aware that using global variables in the way shown above is not ideal. I would prefer not to do this; however, I am adding onto an existing script, and using their style/formatting.
Appending to arrays ($arr += $newItem) in a loop doesn't perform well, because it copies all existing elements to a new array. This should provide better performance:
$ProbDevices = $vm.ExtensionData.Config.Hardware.Device `
| ? { $_.GetType().Name -eq 'VirtualSerialPort' } `
| % { $_.DeviceInfo.Label }

Use GetElementsByClassName in a script

I'm trying to write a PowerShell script to get the text within all the classes named "newstitle" from a website.
This is what I have:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news/
$news=$geturl.parsedhtml.body.GetElementsByClassName("newstitle")[0]
Write-Host "$news"
}
check-krpano
It obviously needs much more tweaking, but so far, it doesn't work.
I managed to write an script using GetElementById, but I don't know the syntax for GetElementsByClassName, and to be honest, I haven't been able to find much information about it.
NOTE:
I've ticked the right answer to my question, but that's not the solution that I had chose to use in my script.
Although I was able to find the content within a tag containing a certain class, using 2 methods, they were much slower that searching for links.
Here is the output using Measure-Command:
Search for divs containing class 'newstitle' using parsedhtml.body -> 29.6 seconds
Search for devs containing class 'newstitle' using Allelements -> 10.4 seconds
Search for links which its element 'href' contains #news -> 2.4 seconds
So I have marked as useful the Links method answer.
This is my final script:
function check-krpano {
Clear-Host
$geturl=Invoke-WebRequest http://krpano.com/news
$news = ($geturl.Links |Where href -match '\#news\d+' | where class -NotMatch 'moreinfo+' )
$news.outertext | Select-Object -First 5
}
check-krpano
If you figure out how to get GetElementsByClassName to work, I'd like to know. I just ran into this yesterday and ran out of time so I came up with a workaround:
$geturl.ParsedHtml.body.getElementsByTagName('div') |
Where {$_.getAttributeNode('class').Value -eq 'newstitle'}
getElementsByClassName does not return an array directly but instead a proxy to the results via COM. As you have discovered, conversion to an array is not automatic with the [] operator. You can use the list evaluation syntax, #(), to force it to an array first so that you can access individual elements:
#($body.getElementsByClassName("foo"))[0].innerText
As an aside, conversion is performed automatically if you use the object pipeline, e.g.:
$body.getElementsByClassName("foo") | Select-Object -First 1
It is also performed automatically with the foreach construct:
foreach ($element in $body.getElementsByClassName("foo"))
{
$element.innerText
}
Cannot, for the life of me, get that method to work either!
Depending upon what you need back in the result though, this might help;
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
$news=($geturl.Links|where href -match '\#news\d+')[0]
$news
}
check-krpano
Gives me back:
innerHTML : krpano 1.16.5 released
innerText : krpano 1.16.5 released
outerHTML : krpano 1.16.5 released
outerText : krpano 1.16.5 released
tagName : A
href : #news1165
You can use those properties directly of course, so if you only wanted to know the most recently released version of krpano, this would do it:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
$news=($geturl.Links|where href -match '\#news\d+')[0]
$krpano_version = $news.outerText.Split(" ")[1]
Write-Host $krpano_version
}
check-krpano
would return 1.16.5 at time of writing.
Hope that achieves what you wanted, albeit in a different manner.
EDIT:
This is a possibly a little faster than piping through select-object:
function check-krpano {
$geturl=Invoke-WebRequest http://krpano.com/news
($geturl.Links|where href -match '\#news\d+'|where class -notmatch 'moreinfo+')[0..4].outerText
}
I realize this is an old question, but I wanted to add an answer for anyone else who might be trying to achieve the same thing by controlling Internet Explorer using the COM object like such:
$ie = New-Object -com internetexplorer.application
$ie.navigate($url)
while ($ie.Busy -eq $true) { Start-Sleep -Milliseconds 100; }
I normally prefer to use Invoke-WebRequest as the original poster did, but I've found cases where it seemed like I needed a full-fledged IE instance in order to see all of the JavaScript-generated DOM elements even though I would expect parsedhtml.body to include them.
I found that I could do something like this to get a collection of elements by a class name:
$titles = $ie.Document.body.getElementsByClassName('newstitle')
foreach ($storyTitle in $titles) {
Write-Output $storyTitle.innerText
}
I observed the same really slow performance the original poster noted when using PowerShell to search the DOM, but using PowerShell 3.0 and IE11, Measure-Command shows that my collection of classes is found in a 125 KB HTML document in 280 ms.
It seems to work with PowerShell 5.1:
function check-krpano {
$geturl = Invoke-WebRequest -Uri "http://krpano.com/news/"
$news = $geturl.ParsedHtml.body.getElementsByClassName("newstitle")
Write-Host "$($news[0].innerHTML)"
}
check-krpano
Output:
krpano 1.20.6<SPAN class=smallcomment style="FLOAT: right"><A href="https://krpano.co
m/forum/wbb/index.php?page=Thread&postID=81651#post81651"><IMG class=icon16m src="../design/ico-forumlink
.png"> krpano Forum Link</A></SPAN>