Tag Archives: Do-Until

Filled-In AD Notes Field


Notice: The following post was originally published on another website. As the post is no longer accessible, it is being republished here on tommymaynard.com. The post was originally published on May 31, 2019.


There’s this thing that might happen. Even as it’s unlikely, I’ve added a protection I’ll probably never even need. I suppose I’d rather be complete and as confident in my code as possible. I don’t want to be the guy, who wrote the code that didn’t protect against the thing. Instead, I’d rather be the guy that wrote the code, that did the thing, even when the protection, was never thought to be needed. It’s like the floor mat at Sears, in front of the escalator, that I saw as a kid that I’ve never forgotten: “Safety First.” It was red, with white writing. There’s plenty of production code out there we’ve written that we’d write differently now, knowing what we’ve already learned.

Just because the code I was working with won’t (likely) ever fill up an Active Directory Group Notes field, doesn’t mean I shouldn’t be sure it never attempts to go over that 1,024 character limit allowed by the field. Knowing that goal, reasonable or otherwise, I set out to use the Notes field and prevent ever exceeding that character limit.

Let’s say, there’s a set of groups in Active Directory — department groups. While we’ll keep their names generic: DEPT 00001, DEPT 00002, etc., we’ll put their true department name in their AD object’s Notes field. In case you don’t know, the attribute name for this field is Info on the back end, and Notes in the UI.

Now, let’s also say that department names change over time. Therefore, it’s bound to happen that DEPT 00001, for example, will need its newest department name added to the Info property. Now, we want to keep all the previous names in the Info property and add the current one to the others. Between each name, we’ll include a pipe character: “|”. This delimiter will assist in helping us determine where one name ends and the next begins. Do consider that department names can change multiple times. If it happens frequently enough, you can see how we might hit our 1,024 character limit in this property. Therefore, it’s a good idea to add in this protection, in order to avoid throwing an error someday.

I’ll include the code I used below, and then we’ll discuss what’s happening further down.

$GroupInfo = $null
$GroupInfo = (Get-ADGroup -Identity $GroupExist.Name -Property Info).Info
If ($GroupInfo) {
	# Edit Notes/Info field (previously populated).
	$GroupInfoSplit = $GroupInfo -split '\|'
	If (-Not($DeptName -in $GroupInfoSplit)) {
		$GroupInfo += "|$DeptName"

	If ($GroupInfo.Length -gt 1024) {
		# Remove previous groups if over 1024 characters.
		Do {
		$GroupInfo = ($GroupInfo -split '\|',2)[1]
		} Until ($GroupInfo.Length -le 1024) # End Do-Until.
	} # End If.

	Set-ADGroup -Identity $GroupExist -Replace @{Info=$GroupInfo}
	} # End If.
} Else {
	# Add to Notes/Info for the first time.
	If ($DeptName.Length -le 1024) {
	        Set-ADGroup -Identity $GroupExist -Add @{Info=$DeptName}
	} # End If.
} # End If-Else

The first thing to know is that this code is inside of a looping construct — a Foreach loop to be exact. Each time we start a new loop, we bring in a different one of our departments, stored in the $GroupExist variable. On line 3, we use its Name property as a part of a Get-ADGroup command and return only the value inside the Info field. Remember, this is the Notes field in the UI. It’s either going to have some data in it, or it’s not. All previously existing departments will already have something in here. It’s new departments that will not.

Once we have, or we don’t have, this data, we go one of two ways. As stated, every department is going to have its department name added to its Notes field. If we don’t return any info from our Get-ADGroup command, we can rest assured, that there’s nothing in this field already and that we only need to add the department name, without any worry about any previously existing data in this field. This If statement begins on line 4, however, in this situation, we’re taking the Else path on line 19. Once there, we check if the length of the department name, stored in $DeptName is longer than 1,024 characters. That will never happen — seriously — but I added the check just in case. I do want to note here that this $DeptName variable changes within our unseen Foreach loop, during each iteration as well.

If there is already a department name or names, then we follow the If path. Let’s deconstruct that, as well. It’s a little more complex.

If our Info/Notes field is already populated, then we need to make some checks before we potentially add a department’s current department name. The first thing we do with the value we’ve returned, as $GroupInfo, is split it at our delimiter — the pipe character on line 6. If there’s only one department name (no delimiter), it will only return that one department name, and that’ll work out just fine. Next, we compare our department name in $DeptName to the value, or values, returned by our split operation. If the current department name is included in our results, we exit — there’s nothing more to do.

Now, if our current department name is not included in our results, we append it to our $GroupInfo variable. At that point, we’ll have this variable with the newest department name appended to the end of this string, where each previous department name is also included with the pipe character in between each. Following this step, our code checks the length of the value stored in $GroupInfo and will only act if its length is greater than those 1,024 characters.

If it is, we run our variable through a Do-Until language construct. For every iteration through the Do-Until, we split our value at the first pipe character from the left of the string and keep everything to the right. We’ll continue to do this, over and over, until our length is less than 1,024 characters.

And that it’s. We can maintain all of a department’s previous department names and ensure they’ll always fit into our Notes field.

Testing Multiplication Facts

Edit: I said I would not, but I added more to the final function at the bottom of this post.

My daughter entered the office right on time — the end of my workday yesterday — and asked me to give her math problems. That means multiplication facts. While she is all but done learning her multiplication tables these days, I guess we are still practicing. It was my job to randomly choose two numbers between 1 and 12 that she would multiply in her head and say the answer out loud. I was sitting an arm’s length away from Windows Terminal and PowerShell, so I was about to give up coming up with the two numbers myself. PowerShell was going to do this for me.

This is not the first time I have discussed multiplication this school year. Here is a related post. This post is not really a part 2.

I didn’t want to spend much of our time writing PowerShell, so I quickly wrote the below PowerShell and we started. Because she could see the problems in the console — I zoomed in like I never zoomed in before — we were able to burn through at least a hundred, maybe two before we wrapped up our  “give me math” session.

Clear-Host; "$(1..12 | Get-Random) x $(1..12 | Get-Random)"

Here is a quick video that is probably worth seeing at this point in the post.

 

As we went through these random multiplication problems, my mind kept going to how I would have written this had I had more time to prepare. Well, that is what this post is for now. I know I can add a ridiculous amount of features, sure, but I have other things to do, so I am going to keep the features to a minimum. It did dawn on me to do something like my 1 to 100 game, but again, I only want to give her enough to play it herself. I did not need to keep score or be as nearly as polished as that one.

$1st = Get-Random -InputObject (1..12)
$2nd = Get-Random -InputObject (1..12)
"$1st x $2nd = $($1st * $2nd)"

3 x 8 = 24

The above example shows some changes to what I had authored previously. Basically, I can create a multiplication problem and include the answer too. This is not very helpful, as my daughter would be able to see the answer. Either way, this was a logical step toward the next example.

In this example, we add a do-until loop, hiding the product from the user. The Read-Host prompt will stop promoting for the answer as soon as it is entered correctly.

$1st = Get-Random -InputObject (1..12)
$2nd = Get-Random -InputObject (1..12)
"$1st x $2nd = ?"
do {$Product = Read-Host -Prompt 'Enter Product'}
until ($Product -eq $1st * $2nd)

1 x 8 = ?
Enter Product: 8

6 x 8 = ?
Enter Product: 45
Enter Product: 50
Enter Product: 48

And that was it. Plenty more could have been added, however, I am not sure she would even appreciate additional features and the extra work. This PowerShell will be good enough. I bet she will think it is great.

Okay fine, I decided to add a little more, but I am done after this, I swear. I put it in a function with a couple of parameters and here it is. As you will see, not much more time was spent doing this.

Function Test-Multiplication {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $FirstRange = (1..12),

        [Parameter()]
        $SecondRange = (1..12)
    )

    $1st = Get-Random -InputObject $FirstRange
    $2nd = Get-Random -InputObject $SecondRange

    "$1st x $2nd = ?"
    do {$Product = Read-Host -Prompt 'Enter Product'}
    until ($Product -eq $1st * $2nd)
}
Test-Multiplication
9 x 9 = ?
Enter Product: 81

Test-Multiplication
4 x 6 = ?
Enter Product: 21
Enter Product: 24

Last note here. Because we have parameters, we can, if we want, send in different ranges for the FirstRange and SecondRange parameters. Ony want to practice your 4’s and you can do this.

Test-Multiplication -FirstRange (4..4)
   
4 x 1 = ?
Enter Product: 4

Test-Multiplication -FirstRange (4..4)
   
4 x 6 = ?      
Enter Product: 24

Okay, I am done now — for real.

Update: Well, it was not for real. I lasted a night. My daughter played the multiplication game and it was clear it needed one more thing. Even though she, at 10, now knows to press the up arrow for the last command, I went ahead and added a little more. Here is the updated function first and then an example of it being executed.

Function Test-Multiplication {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $FirstRange = (1..12),

        [Parameter()]
        $SecondRange = (1..12)
    )

	
	do {
		$1st = Get-Random -InputObject $FirstRange
		$2nd = Get-Random -InputObject $SecondRange

		"$1st x $2nd = ?"
		do {$Product = Read-Host -Prompt 'Enter Product'}
		until ($Product -eq $1st * $2nd)

		do {
			$Replay = Read-Host -Prompt 'Enter = More (Q = quit)' 
		} until ($Replay -eq '' -or $Replay -eq 'Q')
	}
	until ($Replay-eq 'Q')
}
[PS7.2.1][C:\] Test-Multiplication 
4 x 3 = ?      
Enter Product: 12
Enter = More (Q = quit): 
5 x 10 = ?     
Enter Product: 50
Enter = More (Q = quit): 
4 x 5 = ?      
Enter Product: 20
Enter = More (Q = quit): 
9 x 4 = ?      
Enter Product: 35
Enter Product: 39
Enter Product: 36
Enter = More (Q = quit): q
[PS7.2.1][C:\] 

Okay, I am done again, for now. Ugh.

CSV Browser Links

Edit: At some point between publishing this post and now — 11:43 p.m. on Christmas Eve, Eve — I thought people consuming this post would benefit from seeing the menu open a CSV-based bookmark. A short gif has been added to the bottom of this post.

I do not know what my problem is, but for the last few years, I’ve been an anti-browser-bookmarks person. I don’t really have a good reason as to why, but I look forward to getting over it; I do. But until then, I have been saving all my links inside of a CSV file with the assumption that at some point I will write some PowerShell that will allow me to easily search my CSV file and then open links programmatically. Well, guess what, I finally wrote that.

The first thing sharing this project is going to require is a properly formatted CSV file we can both work from. No need to put mine in here; it would be full of links that are worthless to anyone but me. Copy and paste the comma-separated data below and save that off to a CSV file called Links.csv Be sure to note the path where you chose to save it, as we will work with it as a part of this post.

Title,Link,Note
PowerShell GitHub,https://github.com/PowerShell/PowerShell,
PowerShell GitHub Issues,https://github.com/PowerShell/PowerShell/issues,
PowerShell Docs GitHub,https://github.com/MicrosoftDocs/PowerShell-Docs,
Powershell Docs GitHub Issues,https://github.com/MicrosoftDocs/PowerShell-Docs/issues,
PowerShell Docs,https://docs.microsoft.com/en-us/powershell,
PowerShell Gallery,https://www.powershellgallery.com,
PowerShell Reddit,https://www.reddit.com/r/PowerShell,
Twitter Legends @jeffhicks,https://twitter.com/JeffHicks,
Twitter Legends @jsnover,https://twitter.com/jsnover,
Twitter Legends @concentrateddon, https://twitter.com/concentrateddon,
TechCrunch.com,https://techcrunch.com/,Tech News
Cnet.com,https://www.cnet.com/,Tech News
Gizmodo.com,https://gizmodo.com/,Tech News
9to5mac.com,https://9to5mac.com/,Tech News
Engadget.com,https://www.engadget.com/,Tech News
Wired.com,https://www.wired.com/,Tech News
TechRadar.com,https://www.techradar.com/,News
Axios.com,https://www.axios.com/,News

With our CSV in place, we can work through the below PowerShell and then take it for a test drive. Have a look and meet me below for a quick discussion, before we try it out.

function Find-Link {
    [CmdletBinding()]
    Param (
        [Parameter()]
        $Path = 'C:\Users\tommymaynard\Documents\CSVs\Links.csv',
        [Parameter()]$Title,[Parameter()]$Link,[Parameter()]$Note
    )

    #region Import CSV/filter.
    $AllLinks = Import-Csv -Path $Path
    $ FilterLinks = $AllLinks | Where-Object -FilterScript {
        $_.Title -like "*$Title*" -and $_.Link -like "*$Link*" -and $_.Note -like "*$Note*"
    }
    #endregion
    #region Create link menu.
    for ($i = 0; $i -lt $FilterLinks.Count; $i++) {
        "[$($i + 1)] $(($FilterLinks[$i]).Title)"
    } # for
    #endregion
    #region Prompt user.
    do {
        $Option = Read-Host -Prompt 'Link Number'
    } # do
    until ($Option -in (1..$FilterLinks.Count))
    Start-Process -FilePath "$($FilterLinks[$($Option - 1)].Link)"
    #endregion
}

The function is named Find-Link, and it includes a -Path parameter. This is the location of the CSV file. While the function contains a default path, it can be changed when the function is invoked by passing in a different value. The static entry in the function can also be permanently modified, as well — it is up to you.

Find-Link -Path '/users/landrews/Documents/bookmarks.csv'

When the file is imported, the entire CSV is assigned to the $AllLinks variable. Then, it runs a command against that variable, creating a new variable, to filter down the results using the value(s) potentially passed to three other parameters: -Title, -Link, and -Note. There is more than just the -Path parameter.  It does not check if any of these parameters were actually included, but it could have using $PSBoundParameters. Once we have a filtered list of links to display, we cycle through them using a for loop, which creates a menu of options. Here’s an example of one of the outputs created by invoking this command with the -Title parameter.

Find-Link -Title Twitter
[1] Twitter Legends @jeffhicks
[2] Twitter Legends @jsnover        
[3] Twitter Legends @concentrateddon
Link Number:

The final portion of the function is a do-until loop. This invokes Read-Host prompt until one of the available menu numbers is entered. When a number that is not included is entered, it will prompt the user again for a different value. Here is an example of that.

Find-Link -Title Twitter
[1] Twitter Legends @jeffhicks
[2] Twitter Legends @jsnover        
[3] Twitter Legends @concentrateddon
Link Number: 8
Link Number: 15
Link Number: 4
Link Number: 

When a value is selected that is included from the list, Start-Process invokes the corresponding link. Before we close out, here are a few more examples.

Find-Link -Link powershell
[1] PowerShell GitHub
[2] PowerShell GitHub Issues     
[3] PowerShell Docs GitHub       
[4] Powershell Docs GitHub Issues
[5] PowerShell Docs
[6] PowerShell Gallery
[7] PowerShell Reddit
Link Number:
Find-Link -Note News
[1] TechCrunch.com
[2] Cnet.com
[3] Gizmodo.com
[4] 9to5mac.com
[5] Engadget.com
[6] Wired.com
[7] TechRadar.com
[8] Axios.com
Link Number:
Find-Link -Note 'Tech News' 
[1] TechCrunch.com
[2] Cnet.com      
[3] Gizmodo.com   
[4] 9to5mac.com 
[5] Engadget.com
[6] Wired.com   
Link Number:   

We can combine the parameters too, to further filter the results.

Find-Link -Title GitHub -Link powershell
[1] PowerShell GitHub
[2] PowerShell GitHub Issues
[3] PowerShell Docs GitHub
[4] Powershell Docs GitHub Issues
Link Number: 

There may be a few things to add over time, but for now, this gives me what I wanted. It is better than navigating to the document and copying out a link — never. again.

Forum Problem to Posted Solution and Article Post

I was reading the Stack Overflow (PowerShell) forum when I happened on a question that I decided I would take on. It reminded me of an old post I had written. In that post, Countdown Options, I wrote a countdown that overwrote itself as it counted down. Here’s s GIF from that post.

In the question on Stack Overflow, someone wanted to get away from the way Do-Until works. Here’s their, modified-by-me question:

I have this simple bit of code in a PS script where I want to get a simple yes or no answer…The problem is that when user inputs something other than y or n it reprompts on new line…How can I avoid the new line and make the code as simple and easy to understand as possible?

I didn’t bother taking my time to explain that this is how it works and that they should expect more from their users. Instead, I set out to use what I learned from my countdown article. Before we get to that solution, let’s look at how this works before the changes I implemented.

Do {
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

According to the OP, the problem with the above option and below results is that it’s re-prompting on the next line if the answer doesn’t match “y” or “n.” That needed to be avoided, if possible.

Found missing roles. Install them now? (y/n): 1
Found missing roles. Install them now? (y/n): 2
Found missing roles. Install them now? (y/n): 3
Found missing roles. Install them now? (y/n): 4
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): 
Found missing roles. Install them now? (y/n): a
Found missing roles. Install them now? (y/n): b
Found missing roles. Install them now? (y/n): c
Found missing roles. Install them now? (y/n): n

In my example, we overwrite the previously entered value. Take a look at the slight modifications I’ve made to the code. Then, take a look at the GIF made for this article.

$Cursor = [System.Console]::CursorTop
Do {
    [System.Console]::CursorTop = $Cursor
    Clear-Host
    $Answer = Read-Host -Prompt 'Found missing roles. Install them now? (y/n)'
}
Until ($Answer -eq 'y' -or $Answer -eq 'n')

And with that, my work is done here, and over there on Stack Overflow. Well, until the next question, where I can hopefully make a positive impact.