Quantcast
Channel: Hey, Scripting Guy! Blog
Viewing all 3333 articles
Browse latest View live

PowerTip: Create Empty String with PowerShell

0
0

Summary: Learn how to create an empty string by using Windows PowerShell.

Hey, Scripting Guy! Question How can I use Windows PowerShell to create an empty string?

Hey, Scripting Guy! Answer Use the static Empty field from the System.String class:

[string]::Empty


Using the Split Method in PowerShell

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using the Split method in Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, is here. Dude, dude, dude, (or dudette, as the case may be) I still cannot find my teapot after our move. At least I found my kettle to heat water so I can make tea, but unfortunately, without a teapot, I am stuck drinking “bagged” tea leaves. It is not a major crisis yet, but I do miss blending my own teas with my own herbs. And of course, my various tins of teas and herbs are sitting here, just waiting for me to locate the teapot. On the bright side, I did pick up some nice tea bags when the Scripting Wife and I were in Europe, so it is not a total loss. In fact, I got a rather nice oolong tea in little triangle sachets that is actually not bad.

Dude, let's split

The Split method from the System.String class is not a static method. This means that when I have a string, I can gain access to the Split method. But if I do not have a string, I cannot access it. The MSDN documentation for the String.Split Method lists six overloads for this method. So I have a lot of flexibility on how I might want to use the method. I will not discuss all six overloads today, but I will discuss the three main ways of using the method:

  • Split on an array of Unicode characters
  • Split on an array of strings with options
  • Specify the number of elements to return

The additional ways of calling the method will behave in a similar fashion.

Split on an array of Unicode characters

One of the cool things about the Splitmethod is that it will accept an array of things upon which to split. Therefore, I can split on one or more Unicode characters.

Note  A handy list of Unicode characters and their associated code values appears on Wikipedia. When using them in Windows PowerShell, remove the U+ and change it to 0X, then call it with [Char]. For example, the right curly brace is [char]0X007D.

The first thing I need is a string with Unicode characters embedded inside it. So here is my string:

$string = "a powershell $([char]0x007B) string $([char]0x007D) contains stuff"

Now I need to create an array of two characters to use to split the string. Here is what I come up with the first time:

$string.Split("{,}")

And this command works. Here is a screenshot of the string, my split characters, and the associated output:

Image of command output

What is cool is that I can do this same thing in reverse. Here is an example of reversing the process:

PS C:\> $string2 = "a powershell {string} contains stuff"

PS C:\> $string2.Split([char]0x007B, [char]0x007D)

a powershell

string

 contains stuff

PS C:\>

Split on an array of strings with options

The option to specify an array of strings to use for splitting a string offers a lot of possibilities. The StringSplitOptions enumeration also offers a way to control the return of empty elements. The first thing I need to do is to create a string. Here is my string:

$string = "This string is cool. Very cool. It also rocks. Very cool. I mean dude.Very cool. Very cool."

Now, I need to specify a separator. To do this, I will assign an array of strings to the $separator variable as shown here:

$separator = "Very cool.","."

Now, I need to create my StringSplitOption. There are two options: None and RemoveEmptyEntries. The first time I run the command, I will remove the empty entries. So here is that command:

$option = [System.StringSplitOptions]::RemoveEmptyEntries

The command and the output from the command appears here:

Image of command output

When I change the option to None, I see that there is one extra space at the end of the line. This happens because the words “Very Cool.” appear twice at the end of the string. This is shown here:

Image of command output

Specify the number of elements to return

It might be useful to return only a certain number of elements from a string. Here is an example using a string array as a separator:

$string = "This string is cool. Very cool. It also rocks. Very cool. I mean dude. Very cool. Very cool."

$separator = "Very cool.","."

$option = [System.StringSplitOptions]::RemoveEmptyEntries

$string.Split($separator,2, $option)

The first time I ran the command, it generated an error message. The reason is that I added the number of elements to return to the end of the method call. The last position is for the Split option. Here are the commands and the associated output:

Image of command output

That is all there is to using the Split method. String Week will continue tomorrow when I will talk about more string stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell Split Operator to Break Strings

0
0

Summary: Learn how to use the Windows PowerShell Split operator to break up strings.

Hey, Scripting Guy! Question How can I use Windows PowerShell to break a string at a specific character?

Hey, Scripting Guy! Answer Use the Split operator and specify the character to split on, for example:

PS C:\> "this,is,a,string" -split ","

Trim Your Strings with PowerShell

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to trim strings and clean up data.

Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife heads out today to spend time with her other passion at the Blue Ridge Classic Horse Show. Unfortunately, I will not get to attend much of that event due to a week of training that my group is doing. But I will have the chance to see at least one day of the events.

PowerShell Summit Europe

Speaking of the Scripting Wife’s passions, registration is open now for the Windows PowerShell Summit in Europe. The event will be held Sept 29 – Oct 1, 2014 in Amsterdam in The Netherlands. This will be an awesome event, and it will be a great chance to meet other PowerShellers. Teresa and I were in Amsterdam earlier this year. The city is beautiful, and the people were really friendly.

Trim strings using String methods

    Note  This post is the last in a series about strings. You might also enjoy reading:

One of the most fundamental rules for working with data is “garbage in, garbage out.” This means it is important to groom data before persisting it. It does not matter if the persisted storage is Active Directory, SQL Server, or a simple CSV file. One of the problems with “raw data” is that it may include stuff like leading spaces or trailing spaces that can affect sort and search routines. Luckily, by using Windows PowerShell and a few String methods, I can easily correct this situation.

The System.String .NET Framework class (which is documented on MSDN) has four Trim methods that enable me to easily cleanup strings by removing unwanted characters and white space. The following table lists these methods.

Method

Meaning

Trim()

Removes all leading and trailing white-space characters from the current String object.

Trim(Char[])

Removes all leading and trailing occurrences of a set of characters specified in an array from the current String object.

TrimEnd

Removes all trailing occurrences of a set of characters specified in an array from the current String object.

TrimStart

Removes all leading occurrences of a set of characters specified in an array from the current String object.

Trim white space from both ends of a string

The easiest Trim method to use is the Trim() method. It is very useful, and it is the method I use most. It easily removes all white space characters from the beginning and from the end of a string. This is shown here:

PS C:\> $string = " a String "

PS C:\> $string.Trim()

a String

The method is that easy to use. I just call Trim() on any string, and it will clean it up. Unfortunately, the previous output is a bit hard to understand, so let me try a different approach. This time, I obtain the length of the string before I trim it, and I save the resulting string following the trim operation back into a variable. I then obtain the length of the string a second time. Here is the command:

$string = " a String "

$string.Length

$string = $string.Trim()

$string

$string.Length

The command and the associated output from the command are shown in the following image:

Image of command output

Trim specific characters

If there are specific characters I need to remove from both ends of a string, I can use the Trim(char[])method. This permits me to specify an array of characters to remove from both ends of the string. Here is an example in which I have a string that begins with “a “ and ends with“ a”. I use an array consisting of “a”, “ “ and it removes both ends of the string. Here is the command:

$string = "a String a"

$string1 = $string.Trim("a"," ")

The command and its associated output are shown in the image that follows:

Image of command output

The cool thing is that I can also specify Unicode in my array of characters. This technique is shown here:

$string = "a String a"

$string1 = $string.Trim([char]0x0061, [char]0x0020)

 Dr. Scripto says: 

        “Convenient Unicode tables are available on Wikipedia.”

Image of Dr. Scripto

The following image illustrates this technique by displaying the command and the associated output from that command:

Image of command output

Trim end characters

There are times when I know specifically that I need to trim characters from the end of a string. However, those characters might also be present at the beginning of the string, and I need to keep those characters. For these types of situations, I use the TrimEndmethod. The cool thing is that I can automatically use this method three ways:

  1. I can delete a specific character if I type that specific Unicode character code.
  2. I can delete a specific character if I type that specific character.
  3. I can delete Unicode white-space characters if I do nothing but call the method.

Delete a specific character

In this example, I create a string and then specify an array of specific Unicode characters by using the Unicode code value. Because the string begins with the same characters that it ends with, this is a good test to show how I can delete specific characters from the end of the string. Here is the string:

$string = "a String a"

I now specify two Unicode characters by code value to delete from the end of the string. I store the returned string in a variable as shown here:

$string1 = $string.TrimEnd([char]0x0061, [char]0x0020)

The command and the associated output are shown in the following image:

Image of command output

I can also specify the specific character to trim by typing the characters. In this example, I type the< space> a characters as a single array element, so it will only delete <space> a from the end of the string:

PS C:\> $string = "a String a"

PS C:\> $string.Length

10

PS C:\> $string1 = $string.TrimEnd(" a")

PS C:\> $string1

a String

PS C:\> $string1.Length

8

In the following example, I specify the characters as individual elements in an array. In fact, I do not even have them in the same order as they appear in the string. Yet, the results are the same.

PS C:\> $string = "a String a"

PS C:\> $string1 = $string.TrimEnd("a", " ")

PS C:\> $string1.Length

8

PS C:\> $string1

a String

PS C:\>

Delete white space

If I do not specify any characters, the TrimEndmethod automatically deletes all Unicode white-space characters from the end of the string. In this example, a string contains both a space and a tab character at the end of the string. The length is 20 characters long. After I trim the end, the length is only 18 characters long, and both the space and the tab are gone. This technique is shown here:

PS C:\> $string = "a string and a tab `t"

PS C:\> $string1 = $string.TrimEnd()

PS C:\> $string.Length

20

PS C:\> $string1

a string and a tab

PS C:\> $string1.length

18

Trim start characters

If I need to trim stuff from the beginning of a string, I use the TrimStartmethod. It works exactly the same as the TrimEndmethod. So it will also work in three ways as follow:

  1. Delete a specific character from beginning of string if I type that specific Unicode character code.
  2. Delete a specific character from beginning of string if I type that specific character.
  3. Delete Unicode white-space characters from beginning of string if I do nothing but call the method.

Delete specific characters from start

Like the TrimEndmethod, I can specify Unicode characters by Unicode code value. When I do this, the TrimStartmethod deletes those characters from the beginning of a string. This technique is shown here:

PS C:\> $string = "a String a"

PS C:\> $string.Length

10

PS C:\> $string1 = $string.TrimStart([char]0x0061, [char]0x0020)

PS C:\> $string1.Length

8

PS C:\> $string1

String a

Instead of using the Unicode code values, I can simply type the array of string characters I want to delete.

Note  One disadvantage of typing specific characters, is that “ “ is kind of hard to interpret. Is it a space, a tab, or a null value? Is it a typing error, or is it intentional? By using a specific Unicode code value, I know exactly what is meant, and therefore the script is more specific.

In this example, I type specific characters that need to be removed from the beginning of the string:

PS C:\> $string = "a String a"

PS C:\> $string.Length

10

PS C:\> $string1 = $string.TrimStart(" ", "a")

PS C:\> $string1.Length

8

PS C:\> $string1

String a

PS C:\>

Delete white space

If I call the TrimStartmethod and do not supply specific characters, the method will simply delete all Unicode white space from the beginning of the string. This technique is shown here:

PS C:\> $string = "   String a"

PS C:\> $string.Length

11

PS C:\> $string1 = $string.TrimStart()

PS C:\> $string1.Length

8

PS C:\> $string1

String a

PS C:\>

That is all there is to using String methods. This also concludes String Week. Join me tomorrow when I will talk about exploring the Windows PowerShell $Profile variable.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Remove Spaces from Both Ends of String

0
0

Summary: Learn to use Windows PowerShell to remove spaces from both ends of a string.

Hey, Scripting Guy! Question How can I use Windows PowerShell to remove spaces at both ends of a string?

Hey, Scripting Guy! Answer Use the Trim method from the System.String .NET Framework class. 
          In this example, I create a string with a space at each end of the word, then
          I call the Trim method to remove both spaces:

$string = " a String "

$string.Trim()

Note  The Trim method exists on strings, so to use it, simply call it directly from the string. 

Weekend Scripter: Exploring Powershell Profiles

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, explores Windows PowerShell profiles and locations.

Microsoft Scripting Guy, Ed Wilson, is here. One of the cool things about the weekend is that it provides two entire days with no meetings, no specific obligations, and it gives me time to think and reflect. This is also a time to play with Windows PowerShell.

Since Windows PowerShell 1.0 came out, the $profile automatic variable points to the CurrerentUserCurrentHost Windows PowerShell profile. This is extremely helpful because otherwise it would be a heck of a lot of typing, even when using tab expansion. Here is an example:

PS C:\> $PROFILE

C:\Users\ed\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

PS C:\>

See what I mean? Dude. That is a lot of typing. But I do not have to type any of that, because $profile points to the location. The really cool thing is that it points to that location even if I do not have a profile. That is sweet!

But Windows PowerShell has more than one profile. In fact, there are six when one takes into account the Windows PowerShell console and the Windows PowerShell ISE. For more information, see Understanding the Six PowerShell Profiles. I have also written about the profiles, using them, and best practices for managing them. To read more about profiles, see these Hey, Scripting Guy! Blog posts.

Does a profile exist?

I ran into an issue the other day, in that Windows PowerShell was acting a bit funky. I looked in my Windows PowerShell console profile, but I did not see anything wrong. I thought, "Hmmmm. I must have added something somewhere else. But what is the easiest way to see if a profile exists?"

Well, it must be to list out the profile paths, and use Test-Path. If I use Get-Member to list the note properties, and use Force to show hidden properties, I can find the names of the profiles, in additon to the path.

The problem is that to get the path, I would have to use a regular expression to parse the string. This is because $profile returns a string, and the extra profiles are added in as note properties. This is shown here:

PS C:\> $PROFILE | gm -MemberType NoteProperty -Force

   TypeName: System.String

Name                   MemberType   Definition

----                   ----------   ----------

AllUsersAllHosts       NoteProperty System.String AllUsersAllHosts=C:\Windows\System32\WindowsPowerShell\v1.0\profil...

AllUsersCurrentHost    NoteProperty System.String AllUsersCurrentHost=C:\Windows\System32\WindowsPowerShell\v1.0\Mic...

CurrentUserAllHosts    NoteProperty System.String CurrentUserAllHosts=C:\Users\ed\Documents\WindowsPowerShell\profil...

CurrentUserCurrentHost NoteProperty System.String CurrentUserCurrentHost=C:\Users\ed\Documents\WindowsPowerShell\Mic...

I do not like to parse strings with regular expressions if I don’t have to do so. And I know that I can find a specific path to a specific profile by adding a dot to the end of $profile and calling it by name. This is shown here:

PS C:\> $PROFILE.AllUsersAllHosts

C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

Hmmm, so I need to store the profile names in an array, and walk through them after I append them to $profile. First I get the profile names and store them in an array, as shown here:

$p = $PROFILE | gm -MemberType NoteProperty -Force | % {$_.name}

I now check to see if it worked:

PS C:\> $p[0]

AllUsersAllHosts

Yep. So far so good. Now, I decide to walk through the array:

PS C:\> $p | foreach {$PROFILE.$_}

C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1

C:\Users\ed\Documents\WindowsPowerShell\profile.ps1

C:\Users\ed\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

OK. Now I need to find out which profiles exist. I use the Test-Path cmdlet to do this. Here is the command:

PS C:\> $p | foreach {$PROFILE.$_ ; Test-Path $profile.$_}

C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

False

C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1

False

C:\Users\ed\Documents\WindowsPowerShell\profile.ps1

False

C:\Users\ed\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

True

I can see that, in fact, I only have one profile for my Windows PowerShell console. There are still two other profiles that I need to check via the Windows PowerShell ISE. But because my issue was in the console, I need to go into the various modules that I use. Anyway, now I have a starting point. See you tomorrow.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Find All PowerShell Profile Locations

0
0

Summary: Use the hidden properties of $profile to find Windows PowerShell profile locations.

Hey, Scripting Guy! Question How can I find the path to Windows PowerShell profile locations on my computer?

Hey, Scripting Guy! Answer Use the –Force parameter from Format-List to display the hidden properties and values:

$PROFILE | Format-List -Force

Weekend Scripter: Use PowerShell to Fix Broken Printer

0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to fix a printer that keeps losing its duplexer.

Microsoft Scripting Guy, Ed Wilson, is here. One of the cool things about Windows PowerShell is that if I want to, I can generally use it to solve all of my issues. Sometimes the issue is not sufficient that I want to invest the time. However, if it is a persistent issue, and if it occurs often enough, at some point, it might very well tip the scale. The issue I have today is just such a case.

Why can't I use my duplexer?

I believe in printing on both sides of the paper when I have stuff to print out. This, I believe, is a judicious use of resources. Therefore, whenever I buy a printer, I must get one with a duplexer. The printer I have, worked fine, until I upgraded to Windows 8. Now, for some reason, every time I reboot my computer, the printer forgets that it has a duplexer. I suspect this is because the maker of the printer did not update their printer driver.

Three times a month, I go to a writers group, and I have to print a portion of my new book project for peer review. This means that, at a minimum, this issue comes back to haunt me on a recurring basis. If I open Word, I must save my work in Word, close Word, go find my printer, tell the printer it has a duplexer, open Word, find my document and open it, find my spot, and then go into the Word dialog box and tell it to print.

All of this can be enormously monotonous. I have done it often enough that I even know the difference between a printer property and a printer preference. Here is the dialog box:

Image of menu

As I can see from this screenshot, for some reason the printer believes that the duplexer is not installed. I can easily change that, but every time I reboot my computer, it resets to Not installed. I have tried everything I could think of. I have checked for newer printer drivers and I have gone to the print device itself to see if something changed...all to no avail.

This is an issue I have lived with since I upgraded from Windows 7 to Windows 8 to Windows 8.1. I really hoped that upgrading to Windows 8.1 would fix it, but alas, it did not.

Windows PowerShell to the rescue

This little issue is an annoyance, not a real problem. But it is what prompted me to see how to add back in printer options via Windows PowerShell. In fact, I wrote a script to do this very thing:

PS C:\> $hp = Get-Printer -Name *hp*

PS C:\> Set-PrinterProperty -PrinterName $hp.name -PropertyName Config:Duplexer -Value Installed

The previous script worked great for a while, then it quit working.

I decided I needed to do a bit of research as to how all this works. Unfortunately, there is no information about what a permissible PropertyName looks like in the Help. To be honest, however, after doing my research, I am not sure how it could be documented because the property names are set by the people who write the printer driver. I did find a very interesting document. It is the Printer Driver Developers Guide, and it makes for fascinating weekend reading.

The property names themselves are the ones that are in the printer driver data file. So I decided to use Windows PowerShell to retrieve the file and open it in Notepad. The command I used is:

notepad (Get-PrinterDriver -Name *hp*).datafile

The file looks fascinating, and so I decided to take a picture of it. As fascinating as the file is, in the following image, I am on line 1579. The good thing is that I can use the search feature in Notepad to find the information I need. It is shown here:

Image of command output

It looks to me like they changed the name of the feature from Duplexer to DuplexUnit. So, I make a little change. Perfect. It works! Here is the new script:

PS C:\> Set-PrinterProperty -PrinterName $hp.name -PropertyName "Config:DuplexUnit" -Value Installed

PS C:\> Get-PrintConfiguration -PrinterName $hp.name

PrinterName     ComputerName    Collate    Color      DuplexingMode

-----------     ------------    -------    -----      -------------

HP2005DN                        True       False      TwoSidedLongEdge

Another way to find out about available printer properties is to use the Get-PrinterProperty cmdlet, as I use here:

PS C:\> Get-PrinterProperty -PropertyName $hp.name

cmdlet Get-PrinterProperty at command pipeline position 1

Then supply values for the following parameters:

PrinterName: PS C:\> Get-PrinterProperty -PrinterName $hp.name

ComputerName         PrinterName          PropertyName         Type       Value

------------         -----------          ------------         ----       -----

                     HP2005DN             FormTrayTable        String     Config:AutoS...

                     HP2005DN             Config:DuplexUnit    String     Installed

                     HP2005DN             Config:Memory        String     384MB

I can see from the previous output that the property is Config:DuplexUnit. In my previous testing, the Installed value is case sensitive (at least in my testing on my printer), so you may want to keep that in mind.

Note  Of course this cmdlet requires Admin rights, so start the Windows PowerShell console with an elevated account.

Make the change automatic

Now I have found the Windows PowerShell script I need to be able to make the change, and I want to make the change automatically each time my laptop reboots. Therefore, I want a startup job.

Creating a job to run at startup is pretty simple. For more information, read Use PowerShell to Create Job that Runs at Startup.

I decided to use a script block instead of a script because in reality I had a one-liner, and it also makes the job more portable. First I create my startup trigger, then I register the scheduled job. It is two lines of script:

PS C:\> $trigger = New-JobTrigger -AtStartup -RandomDelay 00:00:45

PS C:\> Register-ScheduledJob -Trigger $trigger -ScriptBlock {Set-PrinterProperty -Printer

Name (Get-Printer -Name *hp*).name -PropertyName "Config:DuplexUnit" -Value Installed} -Na

me SetPrinterDuplexer

 

Id         Name            JobTriggers     Command                  Enabled

--          ----                -----------           -------                        -----

2          SetPrinterDu... 1               Set-PrinterProperty -PrinterName (Get... True

Now for the test

I reboot my computer, and the first thing I do is open the Windows PowerShell console with Admin rights to see if my scheduled job ran. As shown here, it did:

Image of command output

Now for the big test. Is my printer set to duplex? I open Control Panel, navigate to my printer, click DeviceSettings, and sure enough, my duplexer is now installed. Sweet.

Image of menu

Two lines of script and I fixed an issue that has vexed me for two years. Not a bad ROI…not bad at all. Windows PowerShell for the win!

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 


PowerTip: Use PowerShell to Display Last Boot Time

0
0

Summary: Learn how to use Windows PowerShell to display last boot up time.

Hey, Scripting Guy! Question How can I use Windows PowerShell to find the last time a workstation booted up?

Hey, Scripting Guy! Answer Use the WMI Class Win32_OperatingSystem and select the LastBootupTime property.
          Using the Get-CimInstance cmdlet returns a properly formatted DateTime object, for example:

(Get-CimInstance Win32_operatingSystem).lastbootuptime

Transforming to Active Directory Cmdlets: Part 1

0
0

Summary: Learn how to translate between ADSI, Quest, and Windows PowerShell cmdlets to create users in Active Directory.

Hey, Scripting Guy! Question Hey, Scripting Guy!

I've got some older scripts that create users by using the [ADSI] accelerator. Can you show me how they might be rewritten to work with the more modern cmdlets in Windows Server 2012 R2?

—DM

Hey, Scripting Guy! Answer Hello DM,

Honorary Scripting Guy, Sean Kearney, is here, and I'm just "primed" to show you how to "transform" from one style of scripting to another!

Windows PowerShell is truly a technology that is more than meets the eye. It can be a simple management console to a full-fledged automation solution. It is so many things to so many people.

One of the challenges faced by IT professionals is taking an older script and bringing it up-to-date. This is, of course, after contemplating the most critical thought process: What is the business case for bringing it up-to-date? If the old script works fine, why change it?

It could be for a reason such as using the newer technology lends to better security, such as in the case of the Microsoft Active Directory cmdlets, which leverage WinRM instead of using RPC.

It could even be that upgrading some scripts to leverage cmdlets (instead of using an older method like ADODB or ADSI) might mean that it would be less difficult to train newer staff.

For whatever reason, this week we're going to assume that you've crossed past the reasoning and there is a valid reason to rewrite some or all of your older scripts. We're also going to focus strictly on Windows PowerShell as opposed to dabbling in VBScript script.

I'm personally of the opinion that if you can adapt your Windows PowerShell script and your VBScript script to work with each other (which it does out of the box), save yourself some time, go home, watch a little Doctor Who, and enjoy the day.

But for the next five days, we're going to visit adapting and transforming between three of the key versions for interfacing Active Directory and Windows PowerShell: the [ADSI] accelerator, Quest cmdlets, and Windows PowerShell cmdlets.

Each has their strengths and each has their weaknesses. But the goal of the next five days is show you the same task in all three worlds so that you can grasp how to adapt between the syntaxes and walk between the three worlds.

So let's try something that saves every administrator a bit of a headache. That pesky locked-out account. You clean them, you scrub them, and no matter what you do, they keep locking.

In my last job, a new coworker (who was already an excellent scripter), joined me in my challenge of maintaining and upgrading our current environment. His name was Kevin.

Kevin's eyes lit up when he heard about Windows PowerShell. He was already trying to find a quicker way to unlock user accounts, which is normally a real…well, you know what's it like.

Kevin had put together a quick Windows PowerShell script to unlock a typical user account, and he was very pleased by it:

$User=[ADSI]"Ldap://CN=Valentine-Michael Smith,OU=Grok,DC=Contoso,DC=Local"

$User.lockouttime = 0

$User.Setinfo

He was about to ask me how to find users in Active Directory rather than typing in the Distinguished Name each time, when I showed him the Quest cmdlets I had been using.

Unlocking the same account with those cmdlets was even easier—a single line. We can pass along the same value to the Quest cmdlet:

Unlock-QADUser –identity 'CN=Valentine-Michael Smith,OU=Grok,DC=Contoso,DC=Local'

But we can even unlock it with only the name from Active Directory:

Unlock-QADUser 'Valentine-Michael Smith'

I think Kevin must have fallen over in his seat when he saw the Quest cmdlet in action. "But wait, there's more!" I popped in.

"More?" he seemed puzzled.

"The Quest cmdlets are a non-Microsoft add-in, and they work great, but your solution is native to the operating system."

There is a new way to work with Windows PowerShell because we have a modern Active Directory domain controller in our environment. Introduced for domain controllers running Windows Server 2008 R2, we can use the Microsoft Active Directory cmdlets, which also happen to be a native solution."

Kevin remembered reading about them, and asked, "So for the same user and the same task, how would this run in the newer cmdlets from Microsoft?"

I looked at Kevin. "They're kinda cooler because you can use them in a manner similar to ADSI, and kinda close to Quest, too. We can unlock an account with only the Distinguished Name like using this method:"

UNLOCK-ADAccount –identity 'CN=Valentine-Michael Smith,OU=Grok,DC=Contoso,DC=Local'

"Or we can let Windows PowerShell find the account and pipe the information to the Unlock cmdlet like this:"

GET-Aduser –filter 'Name –like "Valentine-Michael Smith"' | UNLOCK-ADAccount

Kevin looked over the cmdlets, "So it's not really too tricky to switch between the different methods. Is there an advantage of one over the other?"

I looked at Kevin, "We'll chat about that on Friday, but let's get more stuff that we can convert tomorrow."

Pop in tomorrow when we will learn more about converting between scripting styles!

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your cmdlets each and every day with a dash of Creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Find List of Users in Active Directory

0
0

Summary: Use the Filter parameter in the Active Directory module.

Hey, Scripting Guy! Question I was playing with the Active Directory cmdlets and noticed that they seem to need the Distinguished Name.
           Is there any way to find users instead of supplying this very explicit object each time?

Hey, Scripting Guy! Answer Use the –Filter parameter to pull the information. For example, to find all users with the last name Smith,
           specify the Surname attribute with the –Filter parameter:

GET-ADUser –filter 'Surname –eq "Smith"' 

Transforming the Active Directory Cmdlets: Part 2

0
0

Summary: Learn how to transcribe between ADSI, Quest, and Windows PowerShell cmdlets for creating users.

Hey, Scripting Guy! Question Hey, Scripting Guy!

I used to use the Quest cmdlets to create new users, but I need to rewrite some of my scripts to work with a legacy environment that can't use the .NET Framework. Can you help me?

—SH

Hey, Scripting Guy! Answer Hello SH,

Honorary Scripting Guy, Sean Kearney, is here again. We're going to go into a bit more about translating between the various ways of working with Active Directory and Windows PowerShell. This is the second post in a series. You might also enjoy reading Transforming to Active Directory Cmdlets: Part 1.

Yesterday, I was discussing with my old coworker how to unlock accounts with Windows PowerShell by using the [ADSI] accelerator, the Quest cmdlets, and the modern Windows PowerShell cmdlets for Active Directory.

Let's review when Kevin had to modify one of my scripts. I had a very simple script to create users by using the Quest cmdlets that looked like this:

$First=READ-HOST 'First Name'

$Last=READ-HOST 'Last name'

$Name=$Lastname+', '+$Firstname

$Prefix=$Firstname+'.'+$LastName

$Sam=$Prefix.padright(20).substring(0,20).trim()

$UPN=$Prefix+'@Contoso.local'

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Password='B@dP@ssw0rd!'

NEW-QADUser –firstname $First –lastname $Last –name $Name –parentcontainer $Parent –samaccountname $Sam –upn $UPN –userpassword $Password

When we ran this script, it would simply prompt for the First and Last names and create a disabled user account in Active Directory. The challenge Kevin had was that he needed to work with a vendor appliance, and he could not add the .NET runtime. If he did, it would break its status as a validated environment.

So he needed to easily create multiple users in this environment after they were processed and authorized by the system owner.

So in many respects, most of the script would remain unchanged. We simply needed to modify it to leverage the [ADSI] accelerator instead.

First, we built the targeting link for the binding and specified the type of object we were creating in Active Directory:

$Connection=[ADSI}"LDAP://$Parent"

$Class="user"

Then we add a DisplayName value and add the CN= to the user name because this is required for the older [ADSI] accelerator:

$DisplayName=

$Name='CN=+'$Lastname+', '+$Firstname

With this in place, we can pull out the New-QADUser cmdlet—so the script looks like this:

$First=READ-HOST 'First Name'

$Last=READ-HOST 'Last name'

$DisplayName=$First+' '+$Last

$Name='CN='+$DisplayName

$Prefix=$First+'.'+$Last

$Sam=$Prefix.padright(20).substring(0,20).trim()

$UPN=$Prefix+'@Contoso.local'

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Password='B@dP@ssw0rd!'

# In the following script, we are creating the user with the older [ADSI] accelerator.

$Class="user"

$Connection=[ADSI]"LDAP://$Parent"

$NewUser=$Connection.create($Class, $Name)

$NewUser.put("sAMAccountName",$Sam)

$NewUser.put("userprincipalname",$UPN)

$NewUser.put("Sn",$Last)

$NewUser.put("Givenname",$First)

$NewUser.put("DisplayName",$DisplayName)

$NewUser.setinfo()

$AccountControl=$NewUser.useraccountcontrol

$AccountControl –bxor 2

$NewUser.useraccountcontrol=$AccountControl

$NewUser.setpassword($Password)

$NewUser.setinfo()

Kevin tested the script in the lab to make sure it worked properly. Here is the result:

Image of command output

He then popped in to Active Directory to verify that the user and attributes were created:

Image of menu

"OK, so if I have most of the variables defined, I simply need to find the block of script that has the cmdlet and swap it with the older method," he noted. "So what would it take to make this with the cmdlets in the Active Directory module?"

"Pretty much the same thing," I answered, "except we need to convert the plain text password to a secure string for the Windows PowerShell cmdlets to use. We can use this one line to convert our older variable, $Password:

$SecurePassword=ConvertTo-SecureString $Password -AsPlainText –force

"When we use the Windows PowerShell cmdlets instead of the Quest cmdlets, the script looks like the following example. We simply remove all the code after the command and replace it with this:

New-ADUser -Name $DisplayName -DisplayName $DisplayName -GivenName $First -SamAccountName $Sam -UserPrincipalName $UPN -path $Parent

Get-ADUser -Identity $Sam | Set-ADAccountPassword $SecurePassword

GET-ADAccount -identity $sam | Enable-ADAccount

"This creates the account, sets the password, and then flips the account to active. Pretty nice, eh?"

Kevin nodded, seeing that translating back and forth between scripting styles wasn't tricky after we saw the basics of each cmdlet.

That is it until tomorrow when we will see what other crazy ideas Kevin comes up with!

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your cmdlets each and every day with a dash of creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy

PowerTip: Convert Plain Text to Secure String

0
0

Summary: Learn to convert a plain text password to a secure string for cmdlet parameters.

 Hey, Scripting Guy! Question I'm trying to convert some scripts to work with the Active Directory modules, 
           but they need a secure string for the password. How do I create one of these?

Hey, Scripting Guy! Answer Use the ConvertTo-SecureString cmdlet, capture the output, then
           supply the new variable as SecureString for the password:

$SecurePassword=ConvertTo-SecureString 'MySuperSecretP@ssw0rd!' –asplaintext –force 

Transforming the Active Directory Cmdlets: Part 3

0
0

Summary: Learn how to translate between ADSI, Quest, and Windows PowerShell cmdlets for creating users.

Hey, Scripting Guy! Question Hey, Scripting Guy!

I downloaded a script from the Internet to remove users, but it's designed to use the newer Windows PowerShell cmdlets. My server environment doesn't have them available yet. Can you help me convert to them?

—RD

Hey, Scripting Guy! Answer Hello RD,

Honorary Scripting Guy, Sean Kearney, here. I'm working with my old friend, Kevin. This is the third post in a series. For more, see:

Kevin discovered the power of Bing, and he found a really cool script for purging users. He found a snag though.

"I have to do some work in a remote location, and they don't have a newer domain controller there yet. The change request is still in process, and we need to purge a lot of users. I found a great script to do this, but it only works with the new Windows PowerShell cmdlets."

He sat there with big yellow Labrador eyes blinking, "Can you help out a guy here?"

The script was actually not much more than the following. It provides an Import-CSV to pull in a list of user names, then a cmdlet to delete them:

$PurgeList=IMPORT-CSV PurgeList.Csv

Foreach ($User in $PurgeList)

{

Remove-ADuser $User.Name –confirm:$False

}

Kevin shared some information from the instructions, "I give it a list of SamAccountNames in a CSV file and simply run it through. On the remote site, I've got the Quest cmdlets installed on a management workstation."

"Ah good. So you're not going to accidentally reboot anymore file servers?" I nudged him in the side. Quickly I showed him the Quest cmdlet for removing a user:

REMOVE-QADObject

"Believe it or not, this one behaves almost identically to the Windows PowerShell cmdlet, but instead of using –confirm, we use –force:"

$PurgeList=IMPORT-CSV PurgeList.Csv

Foreach ($User in $PurgeList)

{

Remove-QADobject $User.Name –force

}

Kevin's jaw dropped to the floor. "That's it? I thought there was going to be something cool and challenging about this…"

"Well," I smiled, "It would be if you were asking me to do this with the [ADSI] accelerator. There's a little bit extra there."

Kevin rubbed his hands with glee, "Ah! Fun stuff!"

"Yep. We have to drop in a whole five extra lines after we remove the cmdlet!"

$Name='CN='+$User.Name

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Class="user"

$Connection=[ADSI]"LDAP://$Parent"

$DeleteUser=$Connection.delete($Class, $Name)

So the same script using [ADSI] would look like this:

$PurgeList=IMPORT-CSV PurgeList.Csv

Foreach ($User in $PurgeList)

{

$Name='CN='+$User.Name

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Class="user"

$Connection=[ADSI]"LDAP://$Parent"

$DeleteUser=$Connection.delete($Class, $Name)

}

"Now if you want really hard and scary, we could sit down and this with VBScript."

I've never seen anybody turn around and run so fast.

Pop in tomorrow as Kevin and I have some more "transforming fun" with Active Directory and Windows PowerShell.

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your cmdlets each and every day with a dash of creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Use PowerShell to Determine ASCII Value

0
0

Summary: Learn to use Windows PowerShell to determine the ASCII numeric value of a character.

Hey, Scripting Guy! Question How can I use Windows PowerShell to tell the ASCII value of a character?

Hey, Scripting Guy! Answer You can get the numeric value of an ASCII character quite easily, for example,
           to get the value of the lowercase letter a, type:

[byte][char]'a'


Transforming the Active Directory Cmdlets: Part 4

0
0

Summary: Learn how to translate between ADSI, Quest, and Windows PowerShell cmdlets to read additional user properties.

Hey, Scripting Guy! Question Hey, Scripting Guy!

I'm trying figure something out. I can get some properties with the Quest cmdlets without even asking, but when I go to use the Windows PowerShell cmdlets, they aren't there. How can I get these properties from the system?

—RM

Hey, Scripting Guy! Answer Hello RM,

Honorary Scripting Guy, Sean Kearney, here. Today you can peek in as I guide my old buddy, Kevin, through converting some of his newer Windows PowerShell goodies for Active Directory to some older things.

Note This is the fourth post in a series. For more, see:

Kevin is being asked to audit his company's Active Directory to ensure key properties are populated in all of the user objects. A new application is being brought online to introduce a standard signature in the emails for all staff.

"The problem I've noted," as Kevin states while targeting his head for the shiny spot on the desk, "is that some of the staff are missing the email address and the standard website in their signatures. Although this is fine for a generic address, it's not so good for managers."

I smiled, "So just run a Get-QADUser on them and..."

"I found that the Get-QADUser cmdlet will work fine, but against our 150,000 user Active Directory, it was far too slow. So to test it out, this worked:

$UserInfo=GET-Qaduser Valentine-Michael.Sm

$WebSite=$UserInfo.Webpage

$Email=$Userinfo.Email

"I found that I can pull the properties and write a script around that, but it's very slow—mostly because it pulls a lot of extra information that I don't need. But when I run the same query with the Microsoft version, the date wasn't there. I checked by piping it through Get-Member:

GET-ADUser Valentine-Michael.Sm | GET-Member

Image of command output

"I found that this worked, but it gave me everything, which was again way too slow when working with tens of thousands of users:"

GET-ADUser Valentine-Michael.Sm –properties *

Smiling like a Cheshire Cat, I looked at Kevin, "Too easy, my friend. Let's find the properties that match what you're looking for and specify only them. We can do this with Get-Member, and a little 'wildcarding'."

GET-ADUser Valentine-Michael.Sm –properties * | GET-Member –name *email*

Image of command output

But a few minutes later, he came back. "OK, it worked. But I still can't find the website. I tried both of the following commands...and nothing."

GET-ADUser Valentine-Michael.Sm –properties * | GET-Member –name *web*

GET-ADUser Valentine-Michael.Sm –properties * | GET-Member –name *www*

So I showed Kevin a trick I learned a long time ago. Populate the value with something unique via Active Directory Users and Computers. Then pull all properties and look for that unique value.

GET-Aduser Valentine-Michael.Sm –properties *

In moments, we visually spotted the name as being "Homepage."

Kevin continued, "I remember from Quest that if I want to access multiple properties, I could do it like this:

GET-Qaduser Valentine-Michael.Sm –IncludedProperties MaxStorage,OtherPhone

"I'm guessing that if I type it as follows with the –Properties parameter, I'll get the additional ones I want. I would think they should have a similar format for a list of items."

GET-ADUser Valentine-Michael.Sm –properties OfficeEmail,Homepage

He looked at the screen and found all the details appearing in front of him:

Kevin mused for a moment, "I'll bet these are the same names used by the [ADSI] accelerator because they're using the actual names I would see in the ADSI Editor too. Just for fun, let me try something."

Kevin quickly typed the same query by using the [ADSI] accelerator:

$Name='CN=Valentine-Michael Smith'                                   

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Class="user"

$Connection=[ADSI]"LDAP://$Parent"

$GetUser=$Connection.invokeget($Class, $Name)

$WebSite=$GetUser.InvokeGet("Homepage")

$Email=$GetUser.InvokeGet("EmailAddress")

"AhHa!" he jumped on top of the desk after checking the values, "It works!"

Of course, after his great leap onto the desk, quite a few doors opened to see what happened and why a senior systems analyst would be dancing on his cubicle.

Pop in to tomorrow as we wrap up converting between the Active Directory methods in Windows PowerShell. And maybe settle Kevin down a bit. ;)

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your cmdlets each and every day with a dash of creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: Identify All Properties for Active Directory User

0
0

Summary: Use the –Properties parameter to find all properties attached to an Active Directory user.

Hey, Scripting Guy! Question How can I use Windows PowerShell rather than digging through the ADSI editor to find
           the available properties for a user in our Active Directory?

Hey, Scripting Guy! Answer Leverage the –Properties parameter on a single user to see all available property names:

GET-Aduser Jubal.Harshaw –properties * | Get-Member

Transforming the Active Directory Cmdlets: Part 5

0
0

Summary: Learn how to translate between ADSI, Quest, and Windows PowerShell cmdlets for creating users.

Hey, Scripting Guy! Question Hey, Scripting Guy!

Today I'm translating a script for modifying user properties and I could really use some help. There are so many parameters to work with. Where do I start?

—LC

Hey, Scripting Guy! Answer Hello LC,

Honorary Scripting Guy, Sean Kearney, here. I'm finishing up this week where I have been discussing switching between the various forms of Active Directory cmdlets.

Note This is the final post in a series. For more, see:

Funny enough, LC, Kevin had the same issue. He actually had an older ADSI script that he was trying to modify to both Quest and Windows PowerShell versions. The original script looked like this:

$ModifyList=Import-CSV $ModifyList.csv

Foreach {$User in $ModifyList)

{

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Class="user"

$Connection=[ADSI]"LDAP://$Parent"

$Name='CN='+$User.Name

$Phone=$User.Phone

$Email=$User.Email

$Description=$User.Description

$SetUser=$Connection.($invokeGet(Class, $Name)

$SetUser.put("description",$user.Description)

$SetUser.put("mail",$user.email)

$Setuser.put("telephoneNumber",$user.phone)

}

Kevin was in small quandary, "The cmdlets are easy to use. The issue I am having is trying to sort out the parameters with all the names—there are sooo many."

By looking at Get-Help for both cmdlets for setting user information, you can see Kevin's issue. Check out the sheer number of parameters!

First the Quest cmdlet:

Image of command output

Then the Windows PowerShell cmdlet:

Image of command output

"Using them is actually pretty easy to figure out. Pass the name and give the parameter and value. But I have to try to find the one parameter to match the value I'm looking for. If only there was a way to list the parameters so I could find the name with a search."

Kevin began eyeing the shiny spot when I pointed out, "There is a way...and an easy one at that."

"We get that information by using Get-Command. IT can tell us information about the cmdlet and its properties."

GET-Command SET-ADUser | GET-Member

Image of command output

I then showed Kevin how to pull up a list of parameters for the Set-ADUser cmdlet:

(GET-Command SET-ADUser).parameters

Image of command output

"Ooooooo!" His eyes lit up, "So can I pass this information to Where-Object to filter on?"

He immediately sat down to try to find the parameter for "email" with Set-ADUser:

(GET-Command SET-ADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*mail*' }

He found that the parameter name is EmailAddress, and he began putting together the rest of the command for the Set-ADUser Windows PowerShell cmdlet:

(GET-Command SET-ADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*description*' }

(GET-Command SET-ADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*phone*' }

And then he wrote the following for the Quest version:

(GET-Command SET-QADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*mail*' }

(GET-Command SET-QADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*description*' }

(GET-Command SET-QADUser).parameters | Select-Object -ExpandProperty Keys | where { $_ -like '*phone*' }

In most cases, Kevin found a one-to-one match, for example, Description. In some cases, he got a few extras, for example, HomePhone, MobilePhone, and PhoneNumber.

"But this is way easier. Now that I can see the most likely parameters to use, I can easily convert this script. First, a version for our modern environment with Windows Server 2012 R2:"

$ModifyList=Import-CSV $ModifyList.csv

Foreach {$User in $ModifyList)

{

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Name=$User.Name

$Phone=$User.Phone

$Email=$User.Email

$Description=$User.Description

SET-ADUser $Name –emailaddress $email –OfficePhone $phone –description $Description

}

"And now the exact script for the Quest Set-QADUser cmdlet:"

$ModifyList=Import-CSV $ModifyList.csv

Foreach {$User in $ModifyList)

{

$Parent='OU=Grok,DC=Contoso,DC=Local'

$Name=$User.Name

$Phone=$User.Phone

$Email=$User.Email

$Description=$User.Description

SET-QADUser $Name –email $email –Phonenumber $phone –description $Description

}

Kevin noted that all three commands (ADSI, Quest, and Windows PowerShell) seemed to have their place, "I found that the Quest cmdlets were easiest to use, but they have a .NET Framework requirement that I can't use sometimes—depending on restrictions in the environment. The older [ADSI] accelerator is more involved, but it works all the time."

"True," I nodded, "But the coolest thing you may not have realized about the Microsoft Active Directory cmdlets is that they do not require firewall ports to be open for WMI and RPC. They leverage a web service."

Jaw drop time. "Do you mean the network guys won't freak on me as much when I spin up a domain controller? WooHooo!"

And after this day, a small paper lay on the desk:

"Here lay the 'Shiny Spot'—a place of frustration, venting, and head butting—until Kevin learned Windows PowerShell. The 'Shiny Spot' is no more."

LC, that is all there is to converting the basic Active Directory user cmdlets. You know? I think tomorrow is Saturday! That means time for the Weekend Scripter! WooHoooo!

I invite you to follow The Scripting Guys on Twitter and Facebook. If you have any questions, send an email to The Scripting Guys at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then remember eat your cmdlets each and every day with a dash of creativity.

Sean Kearney, Windows PowerShell MVP and Honorary Scripting Guy 

PowerTip: List All Parameters for a Cmdlet

0
0

Summary: Learn to provide a parameter list for a cmdlet in a viewable table.

Hey, Scripting Guy! Question I am trying to work with cmdlets with so many parameters that I am having a hard time reading them in Get-Help.
           Is there a way to list them in a single table?

Hey, Scripting Guy! Answer Access the Parameters property by using Get-Command and the cmdlet name, for example:

(GET-Command GET-Childitem).parameters

Weekend Scripter: Authentication Silos Part 1

0
0

Summary: Microsoft PFE, Ian Farr, talks about using Windows PowerShell to handle Authentication Policy Silos.

Microsoft Scripting Guy, Ed Wilson, is here. This weekend we have a two-part series from Ian Farr. To read more of Ian's previous guest posts, see these Hey, Scripting Guy! Blog posts.

Legend has it that there exists a great, gilded tome of Active Directory wisdom, maintained by learned elders with long, white beards and silver sandals…

Rumour has it that I might have just made that up!

If such a book were to exist, it should contain the following advice:

  • The Domain Admins group should contain a small number of secondary logon accounts for trusted individuals.
    This is known.
  • Enterprise Admins and Schema Admins groups should be empty and only populated when required.
    This is known.
  • Members of high-privileged groups should only log on to selected servers, for example, enterprise admins only log on to domain controllers.
    This is known.

And it’s the last point that today’s post is about: How do we define and enforce the scope of authentication for high-privileged accounts? The answer lies in new functionality introduced in Windows Server 2012 R2: Authentication Policy Silos.

Here’s what the official documentation has to say about Authentication Policy Silos:

“…Authentication Policy Silos and the accompanying policies provide a way to contain high-privilege credentials to systems that are only pertinent to selected users, computers, or services. Silos can be defined and managed in Active Directory Domain Services (AD DS) by using the Active Directory Administrative Center and the Active Directory Windows PowerShell cmdlets…”

 

Enter Windows PowerShell

Hmmm…Windows PowerShell cmdlets! I wrote a short post about identifying the new cmdlets available in the Active Directory module for Windows PowerShell in Windows Server 2012 R2. If you are interested in learning more, read Cmdlets Roasting on an Open Fire. Here are the cmdlets in all their glory:

Image of cmdlet names

I’m going to focus on these cmdlets today:

I’ll use them to create an Authentication Policy and associated Authentication Policy Silo to ensure that domain admins can only log on to read-write domain controllers.

Prerequisites

Before we can deploy an Authentication Policy and an Authentication Policy Silo, there are some important prerequisites to mention:

  • A domain functional level set to Windows Server 2012 R2.
  • Domain controllers need to have the Supported option configured for the “KDC support for claims, compound authentication, and Kerberos armoring” Group Policy.
    This is located at: Computer Configuration/Policies/Administrative Templates/System/KDC.
  • Clients need to have the “Kerberos client support for claims, compound authentication, and Kerberos armoring” Group Policy enabled.
    This is located at: Computer Configuration/Policies/Administrative Templates/System/Kerberos.

Protected users

It’s also recommended that high-privileged accounts be members of the Protected Users security group. This group has proactive security enhancements designed to prevent credential theft. Members of this group will not be able to:

  • Authenticate with NTLM.
  • Use DES or RC4 encryption types in Kerberos preauthentication.
  • Be delegated with unconstrained or constrained delegation.
  • Renew the Kerberos ticket-granting-tickets (TGTs) beyond the initial four hour lifetime.

Here’s how I added my domain admins to the Protected Users group:

$PrivUsers = Get-ADGroupMember -Identity "Domain Admins"

Add-ADGroupMember -Identity "Protected Users" -Members $PrivUsers

   Note  My demonstration assumes that my Domain Admins group contains only secondary logon accounts.

Authentication Policy

With the prerequisites satisfied, the first thing to do is create an Authentication Policy. This defines Kerberos TGT properties and access control conditions. There are two parts to the process when using Windows PowerShell. Here’s step one:

New-ADAuthenticationPolicy -Name "Reduced_Admin_TGT" `

                           -Description "Authentication policy to set 2 hour Ticket Granting Ticket for administrators" `

                           -UserTGTLifetimeMins 120 `

                           -Enforce `

                           -ProtectedFromAccidentalDeletion $True

In this example, the New-ADAuthenticationPolicy cmdlet creates a policy called Reduced_Admin_TGT, with a shortened TGT lifetime of two hours for users. The policy is enforced (it is recommended that you use audit mode prior to enforcing the policy).

We now have an Authentication Policy that reduces the TGT lifetime of any user in the scope. Step two adds an access control condition. The condition will associate the Authentication Policy with the Authentication Policy Silo that we’ll create next. Here’s how to add a condition for the user TGT lifetime setting that we configured in the last step:

Set-ADAuthenticationPolicy -Identity "Reduced_Admin_TGT" `

                           -UserAllowedToAuthenticateFrom "O:SYG:SYD:(XA;OICI;CR;;;WD;(@USER.ad://ext/AuthenticationSilo == `"Controlled_Admin_Logon`"))"

In this command, the identity is our new Authentication Policy. However, the next parameter is where it gets interesting…

The UserAllowedtoAuthenticateFrom parameter will associate an access control condition with the User portion of an Authentication Policy (you can also have Service or Computer account settings in an Authentication Policy).

In this instance, we’re adding a reference to the “ad://ext/AuthenticationSilo” claim ID, with a value matching the name of the silo: “Controlled_Admin_TGT”, which we’ll create in the next section. This determines which devices users can authenticate from—that is, only those devices in the silo.

Authentication Policy Silo

Now to create the Authentication Policy Silo. An Authentication Policy Silo has to be associated with an existing Authentication Policy.

New-ADAuthenticationPolicySilo -Name "Controlled_Admin_Logon" `

                               -Description "Authentication policy silo to control the scope of logon for administrators" `

                               -UserAuthenticationPolicy "Reduced_Admin_TGT" `

                               -ComputerAuthenticationPolicy "Reduced_Admin_TGT" `

                               -ServiceAuthenticationPolicy "Reduced_Admin_TGT" `

                               -Enforce `

                               -ProtectedFromAccidentalDeletion $True

Here, we configure all the account classes in the silo (User, Computer, and Service accounts) to reference the “Reduced_Admin_TGT” policy. 

You might be thinking that the reduced TGT lifetime from that policy only applies to User accounts… and you’d be right. However, the silo won’t be configured correctly unless all three account types have an associated policy, despite the policy only containing User settings.

Also, I’m enforcing this policy silo. (You would first choose to only audit Authentication Policy Silo outside of a lab.)

Sanity check one

We have:

  • All domain admins in the Protected Users group.
  • An enforced Authentication Policy that restricts user TGTs to two hours and has an access control condition that only allows users to authenticate from devices within a named Authentication Policy Silo.
  • An enforced Authentication Policy Silo that references the aforementioned Authentication Policy.

Onward, dear reader…

Permit accounts

Now, we grant accounts access to the new silo. First let’s add our domain controllers. These will be the devices the users are allowed to authenticate from.

Get-ADDomainController -Filter {IsReadOnly -eq $False} |

ForEach-Object {Grant-ADAuthenticationPolicySiloAccess -Identity "Controlled_Admin_Logon" -Account $_.ComputerObjectDN}

Here we get all read-write domain controllers and supply each computer object’s distinguished name to the Grant-ADAuthenticationPolicySiloAccess cmdlet, which will update the “Controlled_Admin_Logon” Authentication Policy Silo.

Next, let’s add our domain admins to the Authentication Policy Silo. These users will be subject to the “Reduced_Admin_TGT” Authentication Policy, so they will have a two hour TGT and will only be able to authenticate from devices within the same Authentication Policy Silo.

Get-ADGroupMember -Identity "Domain Admins" |

ForEach-Object {Grant-ADAuthenticationPolicySiloAccess -Identity "Controlled_Admin_Logon" -Account $_.DistinguishedName}

Let’s check on the last two actions.

List the members of the silo:

(Get-ADAuthenticationPolicySilo -Identity "Controlled_Admin_Logon").Members

Here is an example from my lab:

Image of command output

Now, I'm also going to run this command:

(Get-ADAuthenticationPolicySilo -Identity "Controlled_Admin_Logon").Members |

Get-ADObject -Properties msDS-AuthNPolicySiloMembersBL

Here’s the output from my lab:

Image of command output

I get the members of the silo and pipe them to Get-ADObject. I also ask for the msDS-AuthNPolicySiloMemberBL attribute, which has been recently populated with the distinguished name of the silo object as a result of adding the User and Computer accounts to the Authentication Policy Silo. (BL stands for back link.)

Associate the accounts

We’re almost there… the final step is to associate the account objects with the new Authentication Policy Silo. We use Set-ADAccountAuthenticationPolicySilo for that. First let’s update each read-write domain controller account with the assistance of an interesting LDAP filter:

Get-ADComputer -LDAPFilter "(&(&(&(samAccountType=805306369)(useraccountcontrol:1.2.840.113556.1.4.803:=8192))))" |

Set-ADAccountAuthenticationPolicySilo –AuthenticationPolicySilo "Controlled_Admin_Logon"

Next, let’s update each Domain Admin account:

Get-ADGroupMember -Identity "Domain Admins" |

Set-ADAccountAuthenticationPolicySilo –AuthenticationPolicySilo "Controlled_Admin_Logon"

Again, let's check our progress. We'll enumerate the silo members, this time asking for an additional attribute: msDS-AssignedAuthNPolicySilo.

(Get-ADAuthenticationPolicySilo -Identity "Controlled_Admin_Logon").Members |

Get-ADObject -Properties msDS-AuthNPolicySiloMembersBL, msDS-AssignedAuthNPolicySilo

Tomorrow we will go to my lab and do some work.

~Ian

Thanks, Ian. I am looking forward to visiting your lab tomorrow.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Viewing all 3333 articles
Browse latest View live




Latest Images