Welcome to TechNet Blogs Sign in | Join | Help

Hey, Scripting Guy! A Scripting Overview of Windows PowerShell 2.0 (Part 1)

Bookmark and Share

(Note: Today's Hey, Scripting Guy! Blog post is an edited excerpt from a forthcoming SAPIEN Press book authored by Don Jones and Jeffery Hicks. Don has published more than 30 books about IT pro topics; you can also find him in TechNet Magazine, on ScriptingAnswers.com, and on concentratedtech.com. Jeffery has authored or co-authored several books in the SAPIEN TFM series; he is a Windows PowerShell MVP, a columnist and contributing editor for Redmondmag.com, and a columnist for MCPMag.com. Find out more about Jeffery at http://jdhitsolutions.com/blog.

Today is part 1 of 2. Part 2 will be published tomorrow. Thanks, Don and Jeffery!)

----------

With many shells—particularly some *nix shells—using the shell interactively is a very different experience than scripting with the shell. Typically, shells offer a complete scripting language that is only available when you’re running a script. Not so with Windows PowerShell: The shell behaves exactly the same, and offers exactly the same features and functionality, whether you’re writing a script or using the shell interactively. In fact, a Windows PowerShell script is a true script—simply a text file listing the things you’d type interactively. The only reason to write a script is because you’re tired of typing those things interactively and want to be able to run them again and again with minimal effort.

Script Files

Windows PowerShell recognizes the .ps1 file name extension as a Windows PowerShell script. Notice the “1” in there? That indicates a script designed to work with Windows PowerShell version 1; future versions of Windows PowerShell will presumably be able to use that as an indicator for backward-compatibility. Script files are simple text files; you can edit them with Notepad or any other text editor. In fact, by default, Windows associates the .ps1 file name extension with Notepad, not Windows PowerShell, so double-clicking a script file opens it in Notepad rather than executing it in Windows PowerShell. Of course, we’re a bit biased against Notepad as a script editor: Notepad was certainly never designed for that task, and better options exist. We’re obviously keen on SAPIEN PrimalScript (www.primalscript.com) because it offers a full visual development environment with Windows PowerShell–specific support, such as the ability to package a Windows PowerShell script in a stand-alone executable that runs under alternate credentials.

Note that Windows PowerShell 2.0 continues to use the .ps1 file name extension; think of this extension as “version 1 of the script file format,” rather than having a direct relation to version 2.0 of the shell. In fact, version 2.0 should run most version 1.0 scripts unchanged.

Profiles

Windows PowerShell supports four special scripts called profiles. These scripts are physically identical to any other script; what makes them special is that Windows PowerShell looks for them when it starts and, if it finds them, executes them. Think of them as a sort of “auto-run” set of scripts, allowing you to define custom aliases, functions, and so forth. For example, by defining custom aliases in your profile, those aliases will be defined every time Windows PowerShell runs, making your aliases available to you anytime you’re using the shell.

Windows PowerShell looks for profiles using a specific path and file name. It looks for them—and executes them, if they’re present—in the following order:

·         %windir%\system32\WindowsPowerShell\v1.0\profile.ps1
This applies to all users and to all shells.

·         %windir\system32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1
This applies to all users but only to the PowerShell.exe shell.

·         %UserDocuments%\WindowsPowerShell\profile.ps1
This applies to the current user but affects all shells.

·         %UserDocuments%\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
This applies to the current user and only to the PowerShell.exe shell.

By the Way…

%UserDocuments% isn’t a valid environment variable. We’re using it to represent the user’s “Documents” folder. On Windows XP, for example, this would be under %UserProfile%\My Documents; on Windows Vista, it’s under %UserProfile%\Documents.

Notice the references to “all shells.” We’re primarily working with the PowerShell.exe shell. However, other shells exist: The Exchange Management Shell (which ships with Exchange Server 2007) is a different shell. If you have things you want defined in all the shells you use—such as custom aliases—you’d put them in one of the “all shells” profiles.

A Note on Shells

Note that you don’t have to use custom shells. For example, you don’t need to use the Exchange Management Shell to manage Exchange Server 2007. Instead, you could simply add the Exchange snap-in to PowerShell.exe, using Add-PSSnapIn. Doing so would give you access to the Exchange cmdlets from the PowerShell.exe shell. This trick allows you to create a shell environment that has all the cmdlets you need to manage all your Windows PowerShell–manageable products.

None of these profile files are created by default. You should also remember that these are just Windows PowerShell scripts, so they won’t run unless they meet your shell’s execution policy. In other words, your profiles need to be signed if your execution policy is AllSigned.

 

See you tomorrow for part 2!

Hey, Scripting Guy! How Do I Format with Windows PowerShell? (Part 2)

Bookmark and Share

(Note: Today's article is written by guest scripter, Thomas Lee. Here is Thomas' scripting autobiography from The Official Scripting Guys Forum! where he is a moderator: I've been scripting pretty much forever. I used to be pretty hot at JCL on the IBM 360 in the late 1960s, did a ton of shell scripting in the 70s on ICL VME. I learned batch scripting with DOS 2.0. I never really grokked VBS (and never got infected with *ix). But I truly “got” Monad when I first saw it in September 2003 and never looked back. I'm proficient in Windows PowerShell 1.0 and 2.0, and specialize in the .NET and WMI aspects. My one interesting fact is that I was the first person to blog about Monad.

Part 2 is today. Part 1 was published yesterday. Thanks again, Thomas!)

----------

 

Formatting Strings Using the .NET Framework

Another approach to creating richer output is to build a nicely formatted string using the built-in formatting capability of the .NET Framework, and then use the Out-Host or similar cmdlet to get Windows PowerShell to display this string on the console. C# programmers are adept at doing this as the .NET Framework provides a wealth of rich formatting functions. The .NET Framework calls this composite formatting. It takes a list of objects and a composite formatting string and outputs nice formatting. The composite formatting string consists of some fixed text plus placeholders, which the Framework calls format items, which correspond to the objects in the list.

 

To see this in action, let's start with a simple query: How many files are contained in the given folder? You can do this relatively easily like this:

(LS C:\FOO).count

You could assign a variable to this calculation, and then output that variable:

$Ccount = (LS C:\FOO).count

$Ccount

On my workstation, both these methods produce the number 67. Now if I just wanted this number, I could quit now, fully satisfied by the default formatting of the output. But if I were creating more structured bit output, I might want it nicely formatted in a string. Composite formatting to the rescue!

 

In Windows PowerShell, you use the -F operator (or -f if you prefer) to invoke the composite formatting feature in Windows PowerShell. The -F operator is really just a call to the Format() method of a string object, which is what does the actual formatting. You supply the composite format string that contains some predefined text and one or more placeholder, and the Framework will format the values you specify for those placeholders.

 

To illustrate this, try something like the following:

$Ccount = (LS C:\FOO\*.ps1).count

"There are {0} in C:\Foo" -f $ccount

Windows PowerShell takes the value on the right of the -F operator (which can be one or more variables or values) and into the composite formatting string. The format item is a value number enclosed in {} characters inside the composite formatting string. The placeholders tell the .NET Framework where to put each value. The first value (in the above case, $ccount) is formatted into placeholder {0}. If there were more values to format, the second is formatted into {1} and so on. The placeholders can be anywhere in the string:

$Ccount = (LS C:\FOO\*.ps1).count

$Today = Get-Date

"Today ({1}) there are {0} files in C:\Foo" -f $ccount,$today


You can also use the placeholder to tell Windows PowerShell how large the space is in which the value is placed and whether the value should be left-aligned or right-aligned within the space. To see this in operation, see the previous image, which shows some different ways to format the count of files.

Using the -F operator, you can create nicely formatted strings and then use these in output to the console. Suppose you wanted to send someone a count of e-mail messages in a mailbox. You could use Exchange EMS cmdlets to get the actual count of e-mail messages, and then use the -F operator to create a nicely formatted string that you send via e-mail.

 

The -F operator provides you with considerable formatting flexibility, and I use it a lot in my scripts. But there's even more you can do to format strings, thanks to the .NET Framework. For more details about using the -F operator to format numbers, see this Windows PowerShell tip. And see this Windows PowerShell tip for more information about formatting date and time. For information about the composite formatting feature, see this article.

 

Using the .ToString() Method

In the .NET Framework, all objects have a .ToString() method that converts an object occurrence such as a specific file into a string. Though all objects have this method, the key is that each fundamental such as integers, real numbers, and strings all have a .ToString method. You can see this by trying:

$i=1

$i.tostring()

Of course, this doesn't do all that much. After all, Windows PowerShell already uses the .ToString() method to output object types already. The real power in this method comes in the parameters you can pass to the .ToString() method. These provide even more detailed instructions about exactly how the .NET Framework is to format the value.

 

This is really useful when you calculate a value but you want to format a value as currency or as a percentage, or when you want to format large numbers with thousands separators and a fixed number of digits to the right of the decimal point. And of course you want to have the currency, thousands separators, and decimal points based on the current culture.

 

For a more detailed look at how the .NET Framework enables you to format any data you wish, see http://msdn.microsoft.com/en-us/library/fbxft59x.aspx. And fasten your seatbelt before you go there and put on protective head gear—your head might hurt! If you lack protective headgear, take a look at this post from the Windows PowerShell Team blog.

 

Using Hash Tables for More Complex Formatting

When you use Format-Table (or Format-List), you usually pipe some objects to the cmdlet and specify which properties you want to see. For example, to get just the name and size of a file, you could type:

Ls c:\foo\*.ps1 | format-table name, length -autosize

The length and name parameters passed to Format-Table are the names of two properties of the objects produced by the LS command. The LS command (okay, the Get-ChildItem cmdlet when run against the FileStore provider) produces both System.IO.FileInfo and System.IO.DirectoryInfo objects; however, in this example where we are looking for *ps1, only the former are produced.

 

In this example, we are passing Format-Table the names of two properties that are to be displayed. The call to Format-Table takes an array of names to format (although when you display more than 4-6 or so properties, the table tends to be a little unusable unless you have a very wide console window or very narrow columns). To provide more flexibility, Windows PowerShell also lets you pass hash tables to Fomat-Table and Format-List instead of property names. The hash table tells Windows PowerShell how to calculate and how to format a particular column (or row because you can also use hash tables when calling Format-List).

 

To show this in action, try the following:

$x1=@{label="Process Name";Expression={$_.name};alignment="right"}

$x2=@{label="CPU Used";Expression={$_.CPU};FormatString="N3"}

Get-Process notepad| Format-Table $x1,$x2 -autosize

In this case, we have created two hash tables ($x1 and $x2) and then call Format-Table specifying these hash tables. You can see the output in the following image. Both hash tables contain the label to be used for the column heading, the expression you use to calculate the value that Windows PowerShell displays. The first hash table includes how the column should be aligned; the second contains a .NET Framework format.

Image of formatting with hash tables


And of course, you can use both hash tables and values when calling either Format-Table or Format-List. For example try this:

$x3=@{label="CPU Used";Expression={$_.CPU};FormatString="N4"}

Get-Process * | Format-Table name,$x3 -autosize

Then try this:

$x4=@{label="CPU Time Used";Expression={$_.CPU};FormatString="N3"}

Get-Process * | Format-List name,$x4

 

Using hash tables with Format-List or Format-Table enables you to create nicely formatted output when you need it. For more details about hash tables and formatting, see this Windows PowerShell tip.

Summary

As we have seen in this article, Windows PowerShell has powerful default formatting. And when you want more, Windows PowerShell provides a wealth of formatting options including Format strings, the -F operator, and hash tables. You can even improve on the PS1XML files to create your own version of default formatting. We will leave the details of that to a future article. About the only limits are your own imagination.

 

Hey, Scripting Guy! How Do I Format with Windows PowerShell? (Part 1)

Bookmark and Share

(Note: Today's article is written by guest scripter, Thomas Lee. Here is Thomas' scripting autobiography from The Official Scripting Guys Forum! where he is a moderator: I've been scripting pretty much forever. I used to be pretty hot at JCL on the IBM 360 in the late 1960s, did a ton of shell scripting in the 70s on ICL VME. I learned batch scripting with DOS 2.0. I never really grokked VBS (and never got infected with *ix). But I truly “got” Monad when I first saw it in September 2003 and never looked back. I'm proficient in Windows PowerShell 1.0 and 2.0, and specialize in the .NET and WMI aspects. My one interesting fact is that I was the first person to blog about Monad.

Part 1 is today. Part 2 will be published tomorrow. Thanks, Thomas!)

----------

 

One of the many challenges IT pros face in managing their systems is getting information out of a computer system. Sometimes, you just want a number, such as how many e-mail messages are in the queue, or how much memory a particular server is using. When it's simple, you don't mind too much if the output is not particularly pretty.

 

But then there are the times you need nicely formatted output that can be read easily on paper or on the screen. You want currency to be presented as currency, not as a decimal number with some random number of digits. Or you want a number expressed as a percentage. In those cases, formatting matters.

 

Among Windows PowerShell many virtues is its ability to get simple output simply. But when you need more control over your output, you can get that too. Pithiness when you want it, richness when you need it. Some of the formatting power comes from Windows PowerShell, and some is built-in to the Microsoft .NET Framework.

 

In this article, I first look at the basics of getting output and Windows PowerShell's default formatting followed by a look at the Format-* cmdlets (and a few more). We then demonstrate how you can leverage the .NET Framework string-handling features before finishing off with using hash tables to get nicer output. As you read this article, why not open a Windows PowerShell window and type along as you read?

Formatting by Default

Most cmdlets produce objects of some sort. For example, in the Windows PowerShell console, type:

LS

As you can see in the following image, you get a nice listing of your current directory. Although you can't see it, the cmdlet actually produces a set of objects that Windows PowerShell formats so that you get output without having to do all the convoluted programming that VBScript users love.

Image of listing of current directory

 

OK, so LS is really an alias of the Get-ChildItem cmdlet, but when issued against the file store provider, it still produces these object types that Windows PowerShell formats into a nice neat table. So how does the output end up looking like the directory listing we are used to?

 

The answer is simple: By default Windows PowerShell performs formatting of all the objects left in the pipeline (or the result of running a single cmdlet). The mechanism by which this happens is less simple but the basics are straightforward.

 

Cmdlets typically leave something in the pipeline. You can demonstrate this by running some arbitrary Windows PowerShell cmdlet or script, and piping it to Get-Member. In the following image, you can see the object types that were produced.

Image of Get-Member


When Windows PowerShell finds objects that are 'left over', it just passes them to Out-Host. And Out-Host does one of two things: For most objects, it makes a call to either Format-Table or Format-List to create output. You can see this behavior by typing:

ls | Format-Table

As you can see if you try this, you get the same output as shown earlier (without having to pipe the output to Format-Table). Windows PowerShell chooses whether to call Format-Table or Format-List based on the number of properties that are to be displayed. By default, if the number of properties that the object contains is five or more, Windows PowerShell calls Format-List; otherwise, it calls Format-Table. The decision about which properties to display for a given object type is based on a set of PS1XML files. These XML files are installed by default, but naturally, you can override or extend them with ease to suit your needs. You can find more details about these files from within Windows PowerShell (Get-Help about_Format.ps1xml), and you can find the files themselves in C:\Windows\System32\WindowsPowerShell.

 

There are a few exceptions  to this approach. The output of the Format-* cmdlets is such that Out-Default can produce output directly. The Format-Table and Format-List cmdlets produce a set of Microsoft.PowerShell.Comands.Internal.Format.* objects (as you can see in in the following image), and Out-Default is smart enough to know when to call a formatting cmdlet and when to send the output directly to the console.

Image of Format.* objects


This means that if you put one of the Format-* cmdlets at the end of a pipeline, you can override the defaults specified in the PS1XML files. And of course, there are some other output methods you can use. For example Out-GridView takes the objects left in the pipeline and formats them into a nice window, which you can see in the following image.

Image of formatting with Out-GridView


This formatting scheme enables you to print by default, or to have total control over what Windows PowerShell outputs. You can specify the properties to display and a whole lot more as we shall see.

Format-List and Format-Table

The two key cmdlets you use to display output on the screen are Format-List and Format-Table. They are very similar commands in structure. Format-Table creates a table with a set of columns, each with a column header. By default, the width of each column is determined by the size of what is being output (a file name, a count, etc). This sometimes results in very wide columns. You can use the -AutoSize parameter to get column widths as large as needed, which you can see in the following image.

Image of formatting with -AutoSize


Both Format-List and Format-Table take the names of the properties of the objects in the pipeline to determine what to output. As you can see in the previous image, I've used just the FullName and BaseName (the name without both the folder and file type) of the files I've selected.

 

For any given object type, you can discover the properties available to you and what they represent by looking in the MSDN Library. Just search for the object's full name and usually the first result is what you want. Looking for System.IO.Fileinfo, the class is defined at http://msdn.microsoft.com/en-us/library/system.io.fileinfo.aspx. The MSDN Library is mainly aimed at developers, but if you are an experienced IT pro, you should be able to get what you need. Additionally, I've been slowly adding Windows PowerShell script samples to illustrate how to use a class. At the bottom of the System.IO.FileInfo page, you'll see a short Windows PowerShell script demonstrating this object type.

 

See you tomorrow for Part 2!

 

Hey, Scripting Guy! Weekend Scripter: Conversion Module, Part 2

Bookmark and Share

 

Microsoft Scripting Guy Ed Wilson here. One of the cool things about writing scripts on the weekend, is the lack of distraction. Because there are few things to compete for one’s attention, there is more time to polish and more time to clean up the code.

For example, consider the ConversionModule.psm1 module I worked on yesterday. After I finished writing it and posted it to the SharePoint site that Craig and I share, I stretched out on the futon in my entertainment room, put on A Midsummer Night’s Dream by Mendelssohn, and sipped a cup of Rooibos tea. I turned on an electric blanket and in my relaxed state thought of several things; I could do to improve the module. Ah, the beauties of weekend scripting. During the rapid pace of the normal workday, I never find time to stretch out on a futon and listen to Mendelssohn.

So what is wrong with the module I wrote yesterday? Not a thing that I know of. It works. Nevertheless, there are a few things to do to improve the function. As seen in the following image some of the information that is returned by the Get-Help cmdlet is incomplete (for example, the Description) and some of the information (such as the data type of the input parameter) is ambiguous.

Image of results of running Get-Help cmdlet


The Description is the easiest problem to fix. To fix this, you need only supply a string value for the .DESCRIPTION tag inside the help area. It is a best practice to use a complete sentence for the description. In addition, I like to include the name of the function in the description because the Synopsis does not generally repeat the function name. The modification is seen in the following image.

Image of modification

 

After the change has been made, save the module and use the Copy-Module.ps1 script to install the module. And after the module has been installed, import it and use the Get-Help cmdlet to see the results. As seen in the following image, the change was successful.

Image of successful change

 

We now need to add the Description tag to each of the other functions. I use copy and paste to copy the original description from the first function and paste it in each of the other functions. After I have completed the copy-and-paste operations, I go back and edit each one in succession.

The next problem that needs to be addressed is the ambiguous data type that is reported for the input parameters. As seen in the following image the feet parameter is reported as an <Object>, which though true is less than illuminating because everything in Windows PowerShell is an object. Data types that are commonly used are integers and strings are represented as [int] and [string], respectively. An integer is a whole number, and when making value conversions from Fahrenheit to Celsius, it is important to be able to use decimal places. What is that data type called? You could open Internet Explorer, navigate over to MSDN, and spend the next 30 minutes or so Binging around trying to arrive at the correct data type. Or you can use the GetType method as seen here and quickly arrive at the System.Double data type:

PS C:\> $a = 1.1111
PS C:\> $a.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Double                                   System.ValueType


PS C:\>

To use Double as a type constraint, you merely place it inside a set of square brackets inside the parameter declaration. This is seen here:

param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $feet
) #end param

Once again, a copy-and-paste operation is in order to fix the remaining parameter statements. The resulting Get-Help output is seen in the following image.

Image of Get-Help output

 

In the full view of help, you can obtain more information about the input types and output types of the script. The .Inputs and the .Outputs tags as shown here control these values.

   .Inputs
    [double]
   .Outputs
    [string]

After the tags have been added to all the functions, and the modified module is installed and imported, you can use Get-Help as seen in the following image.

Image of using Get-Help

 

The modified ConversionModuleV2.psm1 module is seen here.

ConversionModuleV2.psm1

Function ConvertTo-Meters
{
 <#
  .Synopsis
    Converts feet into meters
   .Description
    The ConvertTo-Meters function accepts a value in feet and
    returns a string indicating the number of meters.
   .Example
    ConvertTo-Meters 1
    Converts 1 foot into meters
   .Parameter feet
    The number of feet to be converted
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Meters
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $feet
) #end param
  "$feet feet equals $($feet*.31) meters"
} #end ConvertTo-Meters

Function ConvertTo-Feet
{
 <#
  .Synopsis
    Converts meters into feet
  .Description
    The ConvertTo-Feet function accepts a value in meters and
    returns a string indicating the number of feet.
   .Example
    ConvertTo-Feet 1
    Converts 1 meter into feet
   .Parameter meters
    The number of meters to be converted into feet
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Feet
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $meters
) #end param
 "$meters meters equals $($meters * 3.28) feet"
} #end ConvertTo-Feet

Function ConvertTo-Fahrenheit
{
 <#
  .Synopsis
    Converts celsius into fahrenheit
  .Description
    The ConvertTo-Fahrenheit function accepts a value in celsius and
    returns a string indicating the temperature in Fahrenheit.
   .Example
    ConvertTo-Fahrenheit 1
    Converts 1 degree celsius into fahrenheit
   .Parameter celsius
    The  temperature to be converted into fahrenheit
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Fahrenheit
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $celsius
) #end param
 "$celsius celsius equals $((1.8 * $celsius) + 32 ) fahrenheit"
} #end ConvertTo-Fahrenheit

Function ConvertTo-celsius
{
 <#
  .Synopsis
    Converts fahrenheit into celsius
  .Description
    The ConvertTo-Celsius function accepts a value in fahrenheit and
    returns a string indicating the temperature in celsius.
   .Example
    ConvertTo-Celsius 1
    Converts 1 degree fahrenheit into celsius
   .Parameter fahrenheit
    The  temperature to be converted
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Celsius
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $fahrenheit
) #end param
 "$fahrenheit fahrenheit equals $( (($fahrenheit - 32)/9)*5 ) celsius"
} #end ConvertT-ocelsius

Function ConvertTo-Miles
{
 <#
  .Synopsis
    Converts kilometers into miles
  .Description
    The ConvertTo-Miles function accepts a value in kilometers and
    returns a string indicating the distance in miles.
   .Example
    ConvertTo-Miles
    Converts 1 kilometer into miles
   .Parameter kilometer
    The distance to be converted
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Miles
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $kilometer
) #end param
  "$kilometer kilometers equals $( ($kilometer *.6211) ) miles"
} #end convertToMiles

Function ConvertTo-Kilometers
{
 <#
  .Synopsis
    Converts miles into Kilometers
  .Description
    The ConvertTo-Kilometers function accepts a value in miles and
    returns a string indicating the distance in kilometers.
   .Example
    ConvertTo-Kilometers 1
    Converts 1 mile into kilometers
   .Parameter miles
    The distance to be converted
   .Inputs
    [double]
   .Outputs
    [string]
   .Notes
    NAME:  ConvertTo-Kilometers
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS: WeekEnd Scripter, Modules, Getting Started
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      [Double]
      $miles
) #end param
  "$miles miles equals $( ($miles * 1.61) ) kilometers"
} #end convertTo-Kilometers

If you want to know exactly what we will be covering in the coming week, follow us on Twitter and Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Weekend Scripter: Conversion Module, Part 1

Bookmark and Share 


Microsoft Scripting Guy Ed Wilson here. Well, this is truly a historic day for a number of reasons. The first reason is that this is the first Saturday column in Scripting Guy history! The second historic occasion is that we finally got snow in Charlotte, North Carolina. This second reason is not really historicalwe get snow in Charlotte from time to timebut this snow has been long awaited, much anticipated, and truly appreciated. As seen in the following image, even Dr. Scripto got into the spirit of things as he attempted to make a snow person.

Image of Dr. Scripto

Because we received all the snow and had freezing temperatures, I decided I would like to share the information with my friend Georges who lives in Quebec. When sharing such information with my friends outside the United States, I consider it polite to translate the measurements into metric. I was looking through my conversion functions that we discussed back in December’s Hey, Scripting Guy! Why Would I Even Want to Use Functions in My Windows PowerShell Scripts? post , and I decided it would be more convenient to convert the functions into a module, rather than needing to dot-source the file each time I wanted to access them.

The ConversionFunctions.ps1 script is seen here.

ConversionFunctions.ps1

Function Script:ConvertToMeters($feet)
{
  "$feet feet equals $($feet*.31) meters"
} #end ConvertToMeters

Function Script:ConvertToFeet($meters)
{
 "$meters meters equals $($meters * 3.28) feet"
} #end ConvertToFeet

Function Script:ConvertToFahrenheit($celsius)
{
 "$celsius celsius equals $((1.8 * $celsius) + 32 ) fahrenheit"
} #end ConvertToFahrenheit

Function Script:ConvertTocelsius($fahrenheit)
{
 "$fahrenheit fahrenheit equals $( (($fahrenheit - 32)/9)*5 ) celsius"
} #end ConvertTocelsius

Function Script:ConvertToMiles($kilometer)
{
  "$kilometer kilometers equals $( ($kilometer *.6211) ) miles"
} #end convertToMiles

Function Script:ConvertToKilometers($miles)
{
  "$miles miles equals $( ($miles * 1.61) ) kilometers"
} #end convertToKilometers

So how did I create a module from my conversion functions?

1.     The first thing I did was copy all of the functions into a blank Windows PowerShell ISE window.

2.     Next, I renamed all of the functions.

3.     I then created a template so that I could add help to each of the functions.

4.     Last, I used my Copy-Module.ps1 script to install the newly created module onto my system.

Here is the help template I used:

HelpTemplate.txt

<#
  .Synopsis
    Converts into
   .Example
    ConvertTo-
    Converts 1 into
   .Parameter
    The  to be converted
   .Notes
    NAME:  ConvertTo-
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>

After renaming the functions, and adding help to each function, I saved the module as ConversionModule.psm1. The complete ConversionModule.psm1 module is seen here.

ConversionModule.psm1

Function ConvertTo-Meters
{
 <#
  .Synopsis
    Converts feet into meters
   .Example
    ConvertTo-Meters 1
    Converts 1 foot into meters
   .Parameter feet
    The number of feet to be converted
   .Notes
    NAME:  ConvertTo-Meters
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $feet
) #end param
  "$feet feet equals $($feet*.31) meters"
} #end ConvertTo-Meters

Function ConvertTo-Feet
{
 <#
  .Synopsis
    Converts meters into feet
   .Example
    ConvertTo-Feet 1
    Converts 1 meter into feet
   .Parameter meters
    The number of meters to be converted into feet
   .Notes
    NAME:  ConvertTo-Feet
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $meters
) #end param
 "$meters meters equals $($meters * 3.28) feet"
} #end ConvertTo-Feet

Function ConvertTo-Fahrenheit
{
 <#
  .Synopsis
    Converts celsius into fahrenheit
   .Example
    ConvertTo-Fahrenheit 1
    Converts 1 degree celsius into fahrenheit
   .Parameter celsius
    The  temperature to be converted into fahrenheit
   .Notes
    NAME:  ConvertTo-Fahrenheit
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $celsius
) #end param
 "$celsius celsius equals $((1.8 * $celsius) + 32 ) fahrenheit"
} #end ConvertTo-Fahrenheit

Function ConvertTo-celsius
{
 <#
  .Synopsis
    Converts fahrenheit into celsius
   .Example
    ConvertTo-Celsius 1
    Converts 1 degree fahrenheit into celsius
   .Parameter fahrenheit
    The  temperature to be converted
   .Notes
    NAME:  ConvertTo-Celsius
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $fahrenheit
) #end param
 "$fahrenheit fahrenheit equals $( (($fahrenheit - 32)/9)*5 ) celsius"
} #end ConvertT-ocelsius

Function ConvertTo-Miles
{
 <#
  .Synopsis
    Converts kilometers into miles
   .Example
    ConvertTo-Miles
    Converts 1 kilometer into miles
   .Parameter kilometer
    The distance to be converted
   .Notes
    NAME:  ConvertTo-Miles
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $kilometer
) #end param
  "$kilometer kilometers equals $( ($kilometer *.6211) ) miles"
} #end convertToMiles

Function ConvertTo-Kilometers
{
 <#
  .Synopsis
    Converts miles into Kilometers
   .Example
    ConvertTo-Kilometers 1
    Converts 1 mile into kilometers
   .Parameter miles
    The distance to be converted
   .Notes
    NAME:  ConvertTo-Kilometers
    AUTHOR: Ed Wilson
    LASTEDIT: 1/31/2010
    KEYWORDS:
   .Link
     Http://www.ScriptingGuys.com
 #Requires -Version 2.0
 #>
 [CmdletBinding()]
 param(
      [Parameter(Mandatory = $true,Position = 0,valueFromPipeline=$true)]
      $miles
) #end param
  "$miles miles equals $( ($miles * 1.61) ) kilometers"
} #end convertTo-Kilometers

After I saved the ConversionModule.psm1 file, I used my Copy-Modules.ps1 script to install my new module. After the module is installed, I like to use the Get-Module cmdlet to ensure it has been properly installed. I also like to import the module by using the Import-Module cmdlet to import the module into the current Windows PowerShell session. I then use the Get-Command cmdlet to see which commands have been exported by the module. This is seen here:

PS C:\> C:\fso\Copy-Modules.ps1

cmdlet Copy-Modules.ps1 at command pipeline position 1
Supply values for the following parameters:
path: c:\fso
PS C:\> Get-Module -ListAvailable

ModuleType Name                      ExportedCommands
---------- ----                      ----------------
Script     BasicFunctions            {}
Script     ConversionModule          {}
Script     DotNet                    {}
Manifest   FileSystem                {}
Manifest   IsePack                   {}
Manifest   PowerShellPack            {}
Manifest   PSCodeGen                 {}
Manifest   PSImageTools              {}
Manifest   PSRSS                     {}
Manifest   PSSystemTools             {}
Manifest   PSUserTools               {}
Manifest   TaskScheduler             {}
Manifest   WPK                       {}
Manifest   ActiveDirectory           {}
Manifest   AppLocker                 {}
Manifest   BitsTransfer              {}
Manifest   FailoverClusters          {}
Manifest   GroupPolicy               {}
Manifest   NetworkLoadBalancingCl... {}
Manifest   PSDiagnostics             {}
Manifest   TroubleshootingPack       {}


PS C:\> Import-Module conversion*
PS C:\> Get-Command -Module conversion*

CommandType     Name                                                Definition
-----------     ----                                                ----------
Function        ConvertTo-celsius                                   param($fahrenheit)...
Function        ConvertTo-Fahrenheit                                param($celsius)...
Function        ConvertTo-Feet                                      param($meters)...
Function        ConvertTo-Kilometers                                param($miles)...
Function        ConvertTo-Meters                                    param($feet)...
Function        ConvertTo-Miles                                     param($kilometer)...


PS C:\>

The last thing I do is check the Get-Help function to ensure it is working with my new commands. This is seen here:

PS C:\> Get-Help ConvertTo-kilometers

 

NAME

    ConvertTo-Kilometers

 

SYNOPSIS

    Converts miles into Kilometers

 

SYNTAX

    ConvertTo-Kilometers [-miles] <Object> [<CommonParameters>]

 

DESCRIPTION

 

RELATED LINKS

    Http://www.ScriptingGuys.com

    #Requires -Version 2.0

 

REMARKS

    To see the examples, type: "get-help ConvertTo-Kilometers -examples".

    For more information, type: "get-help ConvertTo-Kilometers -detailed".

    For technical information, type: "get-help ConvertTo-Kilometers -full".

 

PS C:\>

 

I can now tell my friend Georges that the temperature in Charlotte was -3.9 degrees Celsius. This is seen here:

PS C:\> ConvertTo-celsius 25

25 fahrenheit equals -3.88888888888889 celsius

PS C:\>

 

Well, that is how a Scripting Guy spends a snow day in Charlotte, North Carolina.

If you want to know exactly what we will be looking at next week, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (2/5/09)

Bookmark and Share

In this post:

How Can I Run My First VBScript Script?

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I have followed the instructions from the VBScript first steps guide to the letter at least 20 times, but it still does not seem to work. I paste the code into Notepad and save it with the quotation marks. I then open a Command Prompt window, type CScript test.vbs, and press ENTER. These are the results: 

C:\Documents and settings\Denise>cscript test.vbs

Microsft  <R>   Windows script host version 5.7

Copyright  <C>  Microsoft corporation. All rights reserved.

 

Input error: Can not find script file  "C:\Documents and settings\Denise>test.vbs"-

 

Please help! Thank you.

-- HD

 

Hey, Scripting Guy! AnswerHello HD,

Navigating all those folders can be a bear. Perhaps a bear such as this one who was at TechEd 2009. I am going to assume you are on Windows XP, because of the space in Documents and Settings. One of the big improvements in Windows Vista is that we got rid of that space! It makes working from the command line much better.

Follow these steps to run your first VBScript script:

1. Open Notepad.

2. Type Wscript.Echo “my first script”

3. From the File menu, click Save As.

4. Click My Documents.

5. In the File Name box, type test.vbs (it is not case sensitive).

6. Click Save.

7. If a dialog box appears about overwriting an existing file, click OK.

8. From the Notepad File menu, click Exit.

9. Click Start, and then click Run.

10. In the Open box, type cmd, and then click OK.

11. The command prompt appears, and the working directory is C:\Documents and Settings\yourname>. For example, on my computer, it is C:\Documents and Settings\ed>.

12.Type dir and press ENTER. A directory listing appears, one of which should be My Documents.

13. Type cd “My Documents” and press ENTER.

Note: The quotation marks are required. The capital letters are not.

You will now be in the C:\Documents and Settings\yourname\My Documents> folder.

14. Type cscript test.vbs. Inside the cmd prompt, you should see my first script appear.

15. Press the up arrow one time, and you should see Cscript test.vbs appear on the command line.

16. Press the HOME key. You should see the cursor move to the beginning of the command line.

17. Press DELETE eight times until you have deleted Cscript (including the trailing space). This leaves you with only test.vbs in the Command Prompt window.

18. Press ENTER. Now you will see a dialog box that displays the message, my first script.

 

This procedure illustrates creating a script, saving a script, and running a script in the Cscript scripting host, and the Wscript scripting host. It also illustrates that the Wscript.Echo command has two modes of operation. Inside the Cscript scripting host, it displays on the command line. When running inside the Wscript scripting host, dialog boxes are displayed.

I sincerely hope this gets you started in the wonderful world of scripting, and that you stick with this fascinating and amazingly useful skill. (Don’t feel bad that it took you 21 times to get it working. When I first started programming, I spent hours screaming at the walls trying to get my first piece of code to compile. I was even typing things like computername and username in my code [obviously, it didn’t work because those are just place holders for your own computer name and your own user name]). Therefore, I can certainly understand frustrating documentation and confusing instructions. I have tried to keep those early experiences in mind when I wrote my five scripting books and as I write the Hey, Scripting Guy! Blog posts. I do not always succeed, but I do try. Please stay with us, and let me know if I forget my early trials and tribulations! I know you will get it on the next go around.


Can I Use VBScript on Windows 7?
 

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I just purchased a laptop to assist in teaching myself VBScript scripting.  Am I able to do VBScript using Windows 7? I sure hope so! I have attempted to code and save a Notepad text with a .vbs extension with no results.

-- PM

 

Hey, Scripting Guy! AnswerHello PM,

VBScript will work on Windows 7. Here are a couple links for you. First, you may want to visit our Learn to Script page. Next, you will want to visit the Hey, Scripting Guy! archive and look for VBScript articles. You may want to look at the Script Repository and view the thousands of VBScript sample files we have stored Last, you may want to hang out on the Official Scripting Guys Forum.

As far as your specific problem with creating a VBScript file, one thing you might want to check is to see if you are showing file extensions in Windows Explorer. From Tools, click Folder Options. On the View tab, scroll about halfway down and clear the check box for Hide extensions for known file types. This is shown in the following image.

Image of the check box to clear

 

By default file extensions are hidden, and when you save a Notepad file as script.vbs, you are actually saving script.vbs.txt. When saving your script in Notepad, from File click Save As, and in the dialog box that appears, change the Save As Type to All Files (*.*). After you have done this, you should be able to run your script. Windows 7 also includes Windows PowerShell 2.0. If you decide to experiment with that, you will find links from our Learn page as well. 

 

Troubleshooting a VBScript Script

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I read your recent post, but was unable to duplicate your script. Can you include a sample, especially for those of us who are learning the ropes? I have made a folder on the C:\ drive called FSO and added a text file with my local computer name written in it. However, I get an immediate error on line 1 of the VBScript code. What is wrong?

-- KS

 

Hey, Scripting Guy! AnswerHello KS,

The script in the December 31, 2009, Hey, Scripting Guy! Blog post is a Windows PowerShell script, not VBScript. To run the script, you will need to copy and save the script with a .ps1 extension. If you have Windows 7 or Windows Server 2008 R2, you already have Windows PowerShell installed, but you will need to enable Windows PowerShell script support by opening the Windows PowerShell console. Click Start, click All Programs, click Accessories, click Windows PowerShell, and then click Windows PowerShell ISE. Make sure Windows PowerShell ISE is running as an administrator (right-click Windows PowerShell ISE and then click Run as administrator).

On Windows Server 2008 R2, the Windows PowerShell ISE must be added as a feature.

In the bottom of the Windows PowerShell ISE, there is a command console. Type the following command:

Set-ExecutionPolicy remotesigned

A warning will appear that warns you about changing the execution policy. Read it, and then press Y. You can now close the Windows PowerShell ISE and re-open it with normal user rights.

Paste the script from the December 31, 2009, blog post into the top of the Windows PowerShell ISE pane, and then click the green Run Script button (or you can press F5).

If you do not have Windows 7 or Windows Server 2008 R2, you can download and install Windows PowerShell 2.0. I have written a number of Hey, Scripting Guy! Blog posts that talk about getting started with Windows PowerShell. We also have a good learning page for Windows PowerShell.

 

This concludes another edition of Quick-Hits Friday. It also concludes another exciting week on the Script Center. Join us next week as we delve into the mysteries of…we will let that remain a mystery for now.

If you want to know exactly what we will be looking at on Monday, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow for the Weekend Scripter, a brand new series on the Script Center. Check back with us tomorrow to see what all the fuss is about!
 

Ed Wilson and Craig Liebendorfer, Scripting Guys

Hey, Scripting Guy! How Can I Search Active Directory and Produce a Report?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! Is there an echo in here? Why does today's article title sound so familiar? Oh, yeah, and I want to search Active Directory and produce a report. How do I do that?

- DV

SpacerHey, Scripting Guy! Answer

Hi DV,

Scripting Guy Craig Liebendorfer here. As I mentioned yesterday, Ed Wilson and I are working through some "age-induced health 'opportunities'" this week. We really really really do not like to rerun content, but we have to for a couple days. We're human. All too human. (Quick-Hits Friday tomorrow is all new! And next week's content will also be new and feature guest writers.)

Without further ado, we send you to this March 19, 2009, Hey, Scripting Guy! post: 

Hey, Scripting Guy! How Can I Search Active Directory and Produce a Report?

 

Hey, Scripting Guy! How Do I Search Active Directory?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! Why does today's article title sound so familiar? I do want to know how to search Active Directory, but I'm experiencing some deja vu here.

- RR

SpacerHey, Scripting Guy! Answer

Hi RR,

Scripting Guy Craig Liebendorfer here. The rough part about getting older is that part about getting older. Ed Wilson and I are working through some "age-induced health 'opportunities'" this week. We really really really do not like to rerun content, but when you've got IVs jammed into your hands and crochet hook looking ear scrapers sticking out of your ear, you rerun content. We're human. All too human. (Quick-Hits Friday this week is all new! And next week's content will also be new and feature guest writers.)

Without further ado, we send you to this March 16, 2009, Hey, Scripting Guy! post: 

Hey, Scripting Guy! How Do I Search Active Directory?

 

Hey, Scripting Guy! Can I Query Active Directory for Users Whose Passwords Don't Expire?

 Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am interested in using Windows PowerShell to query Active Directory. We have deployed Windows Server 2008 R2 on our domain, and upgraded one of our domain controllers. I would like to be able to query for a list of all users whose passwords do not expire. When I did a Bing search, I was able to find an old Hey, Scripting Guy! post that talks about finding users without expiring passwords, but it was written in VBScript and appears pretty complicated. Is there an easy way to migrate this script to Windows PowerShell 2.0?

-- VN

 

Hey, Scripting Guy! AnswerHello VN,

Microsoft Scripting Guy Ed Wilson here. The Scripting Wife bought me a new stainless steel teapot. I, um, kept breaking the previous teapots she bought. Whoops. I decided to test my new tea pot in preparation for a meeting with the people from PoshCode.org about hosting the 2010 Scripting Games. I made a nice green tea with cinnamon stick and lemon grass. It worked out well, and the meeting went smoothly. We have already begun to announce the 2010 Scripting Games, and registration for the games will be open soon. We are returning the competitive aspect to the games, because it seems scripters are a bloodthirsty lot and want to know who is the best. There will be all kinds of cool prizes and a grand prize for the best scripter who enters the games. We will have certificates, and a script-off if the games are tied at the end. Our partnership with the Microsoft MVPs at PoshCode.org is really beginning to pay off. Stay tuned for more details. If you are on Twitter, create a filter for #2010sg to quickly find the 2010 Scripting Games details when they become available.

To find all of the users in an Organizational Unit (OU) as well as all of the users in child OUs, you need to specify the searchbase. A VBScript that will list all of the users in an OU and in all of the child OUs was the feature of a Hey, Scripting Guy! post several years ago. That script required 17 lines of code to perform the query. In Windows PowerShell 2.0, using the Active Directory module from the Windows Server 2008 R2 Remote Server Admin Tools (RSAT), it is a single line of code. After you have imported the ActiveDirectory module by using the Import-Module cmdlet, you can use the Get-ADUser cmdlet to search and retrieve all of the users from a specific organizational unit (OU).

For more information about installing the RSAT tools on Windows 7 and using the ActiveDirectory module, refer to last Monday’s Hey Scripting Guy! post.

To retrieve all of the users from a specific location, supply the wild card character (“*”) to the -filter parameter. To search recursively beginning at a particular location in Active Directory, use the –searchbase parameter. The beginning search location must be supplied to the –searchbase parameter as a distinguished name value. If you are unsure about what the distinguished name of a particular OU is, you can look it up in the ADSI Edit utility. Most of the time, you will find a link to ADSI Edit in the administrative tools folder. If, for some reason, the shortcut is unavailable, you can always create a custom Microsoft Management Console (MMC) that contains it. To open a blank MMC this, click Start, type mmc in the Search box, and then press ENTER. Or click Start, click Run, type mmc, and click OK.

After you have an empty MMC, you can click Add/Remove Snap-in on the File menu, select ADSI Edit on the Available Snap-ins list, click Add, and then click OK. After you have ADSI Edit added to your MMC, right-click ADSI Edit, click Connect-To, and choose the Default Naming Context. Use the arrows on the left to navigate to the OU you are interested in querying. After you have found the OU you want to query, right-click it and then click Properties in the shortcut menu. The dialog box appears that is shown in the following image.

Image of Properties dialog box

When you have access to all the attributes associated with the organizationalunit object in AD, you can double-click the distinguishedName attribute to get a better look at the value. The dialog shown in the following image appears.

Image of string attribute editor for distinguishedName

To avoid typing a long and convoluted distinguishedname (DN), I will often copy the DN value directly from the dialog box and paste it into the Windows PowerShell console by right-clicking. The completed command is seen here:

PS C:\> Get-ADUser -Filter * -SearchBase "ou=hsg_TestOU,DC=nwtraders,dc=com"


DistinguishedName : CN=HSG_TestChild,OU=HSG_TestOU1,OU=HSG_TestOU,DC=NWTraders,DC=Com
Enabled           : False
GivenName         :
Name              : HSG_TestChild
ObjectClass       : user
ObjectGUID        : 4ffd4a5a-74f3-41ee-ae89-bbb8bd1bd915
SamAccountName    : HSG_TestChild
SID               : S-1-5-21-3746122405-834892460-3960030898-1161
Surname           :
UserPrincipalName :

DistinguishedName : CN=hsgUserGroupTest,OU=HSG_TestOU,DC=NWTraders,DC=Com
Enabled           : False
GivenName         :
Name              : hsgUserGroupTest
ObjectClass       : user
ObjectGUID        : b18e4543-36d5-48e3-b477-389a84ef2d1e
SamAccountName    : hsgUserGroupTest
SID               : S-1-5-21-3746122405-834892460-3960030898-1193
Surname           :
UserPrincipalName :

By default, the Get-ADUser cmdlet will automatically recurse through all of the child OUs. If you do not want to recurse through the child OUs because you are only interested in users from a specific OU, you need to modify the –searchScope parameter. There are actually three values you can supply for the searchscope parameter: base, onelevel, and subtree. By default, the Get-ADUser cmdlet will use the searchscope of subtree (which means to recurse); therefore, there is no need to supply that value. A base searchscope means the query will not enter the searchBase, and in this example does not return any users. A searchScope of onelevel means the search will go into the SearchBase OU, and in this example, one user is returned:

PS C:\> Get-ADUser -Filter * -SearchBase "ou=hsg_TestOU,DC=nwtraders,dc=com" -searchscope onelevel


DistinguishedName : CN=hsgUserGroupTest,OU=HSG_TestOU,DC=NWTraders,DC=Com
Enabled           : False
GivenName         :
Name              : hsgUserGroupTest
ObjectClass       : user
ObjectGUID        : b18e4543-36d5-48e3-b477-389a84ef2d1e
SamAccountName    : hsgUserGroupTest
SID               : S-1-5-21-3746122405-834892460-3960030898-1193
Surname           :
UserPrincipalName :



PS C:\>

The hierarchy we are searching, as viewed from Active Directory Users and Computers, is seen in the following image.

Image of Active Directory Users and Computers hierarchy

One of the really cool things you can do with the AD DS cmdlets is use the –LDAPFilter parameter. For example, with the Get-ADUser cmdlet, perhaps you would like to retrieve all of the users who have a password that does not expire. A Hey, Scripting Guy! post from several years ago included such a script. In the script, there was a rather complicated LDAP query that returned the users whose password does not expire. The applicable portion of the script is seen here:

objCommand.CommandText = _
    "<LDAP://dc=fabrikam,dc=com>;" & _
        "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536));" & _
            "Name;Subtree"

An LDAP dialect query is made up of four portions. The first portion is the location of the LDAP query; the second is the LDAP filter; the third portion is the properties to return; and the last is the search scope. With the Get-ADUser cmdlet, we have parameters for each of those four parameters. We are therefore only interested in the LDAP filter query seen here:

"(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536));"

When copying the LDAP filter query, do not include the trailing semicolon. That is an artifact of the previous query; each portion of an LDAP dialect query is separated by semicolons. The query goes directly into the parameter. There is no need to escape anything with extra quotation marks or anything else. The following command is a single-line command that will wrap when typed into the Windows PowerShell console. It accomplishes in one line the same thing the earlier Scripting Guy VBScript took 17 lines to do:

PS C:\> Get-ADUser -SearchBase "dc=nwtraders,dc=com" -searchscope subtree -ldapfilter "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536))"


DistinguishedName : CN=Guest,CN=Users,DC=NWTraders,DC=Com
Enabled           : False
GivenName         :
Name              : Guest
ObjectClass       : user
ObjectGUID        : 5b0b1ce3-808f-4976-b80e-356adc1dc7e6
SamAccountName    : Guest
SID               : S-1-5-21-3746122405-834892460-3960030898-501
Surname           :
UserPrincipalName :

DistinguishedName : CN=another User,OU=HSG_TestOU,DC=NWTraders,DC=Com
Enabled           : True
GivenName         : another
Name              : another User
ObjectClass       : user
ObjectGUID        : f8315b67-ee23-486d-9bfc-a5e118f0392a
SamAccountName    : anotherUser
SID               : S-1-5-21-3746122405-834892460-3960030898-1202
Surname           : User
UserPrincipalName : anotherUser@NWTraders.Com

DistinguishedName : CN=AHappy Camper,OU=HSG_TestOU,DC=NWTraders,DC=Com
Enabled           : True
GivenName         : AHappy
Name              : AHappy Camper
ObjectClass       : user
ObjectGUID        : 6852f2bc-37d5-46b5-b6da-4365c7009dfc
SamAccountName    : AHappyCamper
SID               : S-1-5-21-3746122405-834892460-3960030898-1203
Surname           : Camper
UserPrincipalName : AHappyCamper@NWTraders.Com



PS C:\>

 

VN, that is all there is to using the AD DS cmdlets to search User objects. Searching Active Directory Week will continue tomorrow.

If you want to know exactly what we will be discussing tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace. 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Use Windows PowerShell to Search Active Directory?

 Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I often need to search Active Directory Domain Services (AD DS) to find information about various computers. I may need to identify all of the computers in a particular organizational unit or all the computers who happen to reside in a particular office. Whatever the reason, using Active X Data Objects (ADO) like I did in the old VBScript days is a major pain. When I learned how to use the Directory Searcher Object, it was a little better—in fact quite a bit better, but I am curious if the new Active Directory cmdlets have anything to make it easier to retrieve users?

-- NN

 

Hey, Scripting Guy! AnswerHello NN,

Microsoft Scripting Guy Ed Wilson here. Today is a special day because I finally received my copies of the Windows PowerShell 2.0 Best Practices book that I spent 18 months of my life writing. For me, the big thing about writing books is the enjoyment I receive when people tell me they have read my book and it helped them. On Facebook this morning, I had two notes from people who stated they had enjoyed my books. Speaking of Facebook, send me a friend request. I enjoy the interaction. In addition to the Best Practices book, the morning mail also showed up with a tin of Genmaicha tea. This delicate green tea is best brewed for 35 minutes, and tastes great with a cinnamon stick in it. The beautiful blue sky, cool temperature, a special pot of green tea, and an excellent Windows PowerShell book make a wonderful way to spend the day. But I also need to check the e-mail sent to scripter@microsoft.com.

It is a bit damp outside today, but the sky is clear. It reminds me of Lima, Peru, when I was down there teaching a VBScript class a few years ago. My friend Omar took me around, and I was able to take some great pictures such as the following one.

Image of Lima, Peru

 

NN, I agree with you completely. Using the Directory Searcher .NET Framework classes is easier to use than the old fashioned ADO or even new fangled ADO.NET scripts. In the end I feel it is a best practice to use what you are comfortable with, and to use what will be easiest for you to modify, to troubleshoot, and to maintain.

An example of using ADO to query AD DS is the QueryAD.Ps1 script I wrote several years ago.

QueryAD.ps1

param(
      $ou,
      $domain,
      $query,
      [switch]$help
     )

function funHelp()
{
 $helpText=@"
 DESCRIPTION:
 NAME: QueryAD.ps1
 Queries Active Directory  on a local or remote machine.

 PARAMETERS:
 -ou        the organizational unit to query
 -domain    the domain to query
 -query     the query to use. Queries for objects such as:
            < User, Group, Computer, OrganizationalUnit,
              printqueue, grouppolicycontainer, ipsecpolicy,
              pkicertificatetemplate, sitelink, subnet, site >
 -help      prints help file

 SYNTAX:
 QueryAD.ps1

 Generates message of missing parameter and displays help

 QueryAD.ps1 -domain "nwtraders.com" -ou "mytestou" -query computer

 Displays a listing of every computer object in the mytestou organizational
 unit of the nwtraders.com domain

 
 QueryAD.ps1 -help

 Prints the help topic for the script
"@
  $helpText
  exit
} #end funHelp

Function funQueryAD()
{
 $domain = $domain -replace("^","dc=")    #replace first character
 $domain = $domain -replace("\.",",dc=") #replace the period

 if(!$ou)
  {
   if(!$query)
    {
     $strQuery = "<LDAP://$domain>;;name;subtree"
    }
   ELSE
    {
     $strQuery = "<LDAP://$domain>;(objectcategory=$query);name;subtree"
    }
  }
 ELSE
  {
   $ou = $ou -replace("^","ou=")      #replace first character
   $ou = $ou -replace("\,",",ou=") #replace a comma
   if(!$query)
    {
     $strQuery = "<LDAP://$ou,$domain>;;name;subtree"
    }
   ELSE
    {
     $strQuery = "<LDAP://$ou,$domain>;(objectcategory=$query);name;subtree"
    }
  }


 $objConnection = New-Object -comObject "ADODB.Connection"
 $objCommand = New-Object -comObject "ADODB.Command"
 $objConnection.Open("Provider=ADsDSOObject;")
 $objCommand.ActiveConnection = $objConnection
 $objCommand.CommandText = $strQuery
 $objRecordSet = $objCommand.Execute()

 Do
 {
     $objRecordSet.Fields.item("name") |Select-Object name,Value
     $objRecordSet.MoveNext()
 }
 Until ($objRecordSet.eof)

 $objConnection.Close()
} #end funQueryAD

if($help) { "calling help ..." ; funhelp }
if(!$domain) { "missing the domain name" ; funhelp }
if(!$domain -or !$ou -or !$query) { "a parameter is required" ; funhelp }
funqueryAD

By using the Directory Searcher object, you can reduce significantly the amount of work that is involved in querying Active Directory. The SearchAllComputersInDomain.ps1 script was used for a Hey, Scripting Guy! Blog post in March 2009 when we spent a week talking about searching Active Directory.

SearchAllComputersInDomain.ps1

$Filter = "ObjectCategory=computer"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)
$Searcher.Findall() |
Foreach-Object `
  -Begin { "Results of $Filter query: " } `
  -Process { $_.properties ; "`r"} `
  -End { [string]$Searcher.FindAll().Count + " $Filter results were found"

In Windows PowerShell 2.0, you can shorten the script a bit by using the [adsisearcher] type accelerator. The [adsisearcher] type accelerator saves you the trouble of creating an instance of the DirectoryServices.DirectorySearcher .NET Framework class. This is seen here.

SearchComputersUseAdsiSearcher.ps1

$Filter = "ObjectCategory=computer"
$Searcher = [adsiSearcher]($Filter)
$Searcher.Findall() |
Foreach-Object `
  -Begin { "Results of $Filter query: " } `
  -Process { $_.properties ; "`r"} `
  -End { [string]$Searcher.FindAll().Count + " $Filter results were found" }

If you have at least one Windows Server 2008 R2 domain controller and the Remote Server Administration Tools for Windows 7 (RSAT) tools installed, you can use the Get-ADComputer cmdlet to retrieve information about a computer account in AD DS. The –identity parameter will accept the samAccountName, the DistinguishedName, the security identifier (SID), or the object GUID:

PS C:\> Get-ADComputer -Identity hyperv

DistinguishedName : CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com
DNSHostName       : HyperV.NWTraders.Com
Enabled           : True
Name              : HYPERV
ObjectClass       : computer
ObjectGUID        : 2a76b1bd-80cb-4546-a8f2-ea46d474e06a
SamAccountName    : HYPERV$
SID               : S-1-5-21-3746122405-834892460-3960030898-1000
UserPrincipalName :


PS C:\> Get-ADComputer -Identity 'CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com'

DistinguishedName : CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com
DNSHostName       : HyperV.NWTraders.Com
Enabled           : True
Name              : HYPERV
ObjectClass       : computer
ObjectGUID        : 2a76b1bd-80cb-4546-a8f2-ea46d474e06a
SamAccountName    : HYPERV$
SID               : S-1-5-21-3746122405-834892460-3960030898-1000
UserPrincipalName :


PS C:\> Get-ADComputer -Identity S-1-5-21-3746122405-834892460-3960030898-1000

DistinguishedName : CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com
DNSHostName       : HyperV.NWTraders.Com
Enabled           : True
Name              : HYPERV
ObjectClass       : computer
ObjectGUID        : 2a76b1bd-80cb-4546-a8f2-ea46d474e06a
SamAccountName    : HYPERV$
SID               : S-1-5-21-3746122405-834892460-3960030898-1000
UserPrincipalName :


PS C:\> Get-ADComputer -Identity 2a76b1bd-80cb-4546-a8f2-ea46d474e06a

DistinguishedName : CN=HYPERV,OU=Domain Controllers,DC=NWTraders,DC=Com
DNSHostName       : HyperV.NWTraders.Com
Enabled           : True
Name              : HYPERV
ObjectClass       : computer
ObjectGUID        : 2a76b1bd-80cb-4546-a8f2-ea46d474e06a
SamAccountName    : HYPERV$
SID               : S-1-5-21-3746122405-834892460-3960030898-1000
UserPrincipalName :

Because the identity parameter is the default parameter for Get-ADComputer, you can leave it out and just supply the name of the computer you wish to query. This is seen here:

PS C:\> Get-ADComputer win7-pc


DistinguishedName : CN=WIN7-PC,CN=Computers,DC=NWTraders,DC=Com
DNSHostName       : WIN7-PC.NWTraders.Com
Enabled           : True
Name              : WIN7-PC
ObjectClass       : computer
ObjectGUID        : 3e802bb2-702a-4039-90dd-d7b624c97440
SamAccountName    : WIN7-PC$
SID               : S-1-5-21-3746122405-834892460-3960030898-1103
UserPrincipalName :

One strange thing is the use of the property parameter from the Get-ADComputer cmdlet. You would expect that piping the computer object that is returned by the cmdlet to the Format-List cmdlet would provide you the opportunity to work with computer object properties. When working with other objects, you can use the wildcard character “*” and the force switch with the Format-List cmdlet and retrieve all properties and values of an object. As seen here, when working with the Get-ADComputer cmdlet, that is not the case:

PS C:\> Get-ADComputer win7-pc | format-list * -Force


DistinguishedName : CN=WIN7-PC,CN=Computers,DC=NWTraders,DC=Com
DNSHostName       : WIN7-PC.NWTraders.Com
Enabled           : True
Name              : WIN7-PC
ObjectClass       : computer
ObjectGUID        : 3e802bb2-702a-4039-90dd-d7b624c97440
SamAccountName    : WIN7-PC$
SID               : S-1-5-21-3746122405-834892460-3960030898-1103
UserPrincipalName :
PropertyNames     : {DistinguishedName, DNSHostName, Enabled, Name...}
PropertyCount     : 9



PS C:\>

To obtain all of the information available from a computer object, you must use the property parameter from the Get-ADComputer cmdlet, as seen here:

PS C:\> Get-ADComputer -Identity win7-pc -Properties *


AccountExpirationDate              :
accountExpires                     : 9223372036854775807
AccountLockoutTime                 :
AccountNotDelegated                : False
AllowReversiblePasswordEncryption  : False
BadLogonCount                      : 0
badPasswordTime                    : 0
badPwdCount                        : 0
CannotChangePassword               : False
CanonicalName                      : NWTraders.Com/Computers/WIN7-PC
Certificates                       : {}
CN                                 : WIN7-PC
codePage                           : 0
countryCode                        : 0
Created                            : 9/8/2009 9:48:38 PM
createTimeStamp                    : 9/8/2009 9:48:38 PM
Deleted                            :
Description                        :
DisplayName                        :
DistinguishedName                  : CN=WIN7-PC,CN=Computers,DC=NWTraders,DC=Com
DNSHostName                        : WIN7-PC.NWTraders.Com
DoesNotRequirePreAuth              : False
dSCorePropagationData              : {12/3/2009 6:32:30 PM, 12/3/2009 6:32:29 PM, 12/2/2009 7:18:22 AM, 12/2/2009 7:18:
                                     22 AM...}
Enabled                            : True
HomedirRequired                    : False
HomePage                           :
instanceType                       : 4
IPv4Address                        : 192.168.1.110
IPv6Address                        :
isCriticalSystemObject             : False
isDeleted                          :
LastBadPasswordAttempt             :
LastKnownParent                    :
lastLogoff                         : 0
lastLogon                          : 129084954052650603
LastLogonDate                      : 1/15/2010 12:28:29 PM
lastLogonTimestamp                 : 129080501096745399
localPolicyFlags                   : 0
Location                           :
LockedOut                          : False
logonCount                         : 255
ManagedBy                          :
MemberOf                           : {}
MNSLogonAccount                    : False
Modified                           : 1/18/2010 8:41:55 AM
modifyTimeStamp                    : 1/18/2010 8:41:55 AM
msDS-SupportedEncryptionTypes      : 28
msDS-User-Account-Control-Computed : 0
Name                               : WIN7-PC
nTSecurityDescriptor               : System.DirectoryServices.ActiveDirectorySecurity
ObjectCategory                     : CN=Computer,CN=Schema,CN=Configuration,DC=NWTraders,DC=Com
ObjectClass                        : computer
ObjectGUID                         : 3e802bb2-702a-4039-90dd-d7b624c97440
objectSid                          : S-1-5-21-3746122405-834892460-3960030898-1103
OperatingSystem                    : Windows 7 Enterprise
OperatingSystemHotfix              :
OperatingSystemServicePack         :
OperatingSystemVersion             : 6.1 (7600)
PasswordExpired                    : False
PasswordLastSet                    : 1/18/2010 8:41:55 AM
PasswordNeverExpires               : False
PasswordNotRequired                : False
PrimaryGroup                       : CN=Domain Computers,CN=Users,DC=NWTraders,DC=Com
primaryGroupID                     : 515
ProtectedFromAccidentalDeletion    : False
pwdLastSet                         : 129082957152111233
SamAccountName                     : WIN7-PC$
sAMAccountType                     : 805306369
sDRightsEffective                  : 15
ServiceAccount                     : {}
servicePrincipalName               : {WSMAN/win7-PC, WSMAN/win7-PC.NWTraders.Com, TERMSRV/WIN7-PC, TERMSRV/win7-PC.NWTr
                                     aders.Com...}
ServicePrincipalNames              : {WSMAN/win7-PC, WSMAN/win7-PC.NWTraders.Com, TERMSRV/WIN7-PC, TERMSRV/win7-PC.NWTr
                                     aders.Com...}
SID                                : S-1-5-21-3746122405-834892460-3960030898-1103
SIDHistory                         : {}
TrustedForDelegation               : False
TrustedToAuthForDelegation         : False
UseDESKeyOnly                      : False
userAccountControl                 : 4096
userCertificate                    : {}
UserPrincipalName                  :
uSNChanged                         : 271739
uSNCreated                         : 12748
whenChanged                        : 1/18/2010 8:41:55 AM
whenCreated                        : 9/8/2009 9:48:38 PM



PS C:\>

NN, that is all there is to using Windows PowerShell to search Active Directory. Searching Active Directory Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! Quick-Hits Friday: The Scripting Guys Respond to a Bunch of Questions (1/29/09)

Bookmark and Share

In this post:

 

How Can I Run This Windows PowerShell Command on a Remote Computer?

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I wanted to run the DU tool on several servers in my environment. Is there a way to script this request via Windows PowerShell? I can run it for one server by doing something like this.

PS> du -v "z:\windows" | sort -Descending | Out-File c:\du_test.txt

The Z: drive represents a mapping of the target server C: drive.

-- JC

 

Hey, Scripting Guy! AnswerHello JC,

Microsoft Scripting Guy Ed Wilson here. I would use Windows PowerShell 2.0 and remote the command. It is the easiest way to do this. You can download Windows PowerShell 2.0 for Windows Server 2008, Windows Vista, Windows XP, and Windows Server 2003. For Windows 7 and Windows Server 2008 R2, there is no need to download Windows PowerShell 2.0 because it is built into the operating system.

After you have installed Windows PowerShell 2.0, take a look at the about_Remoting articles in the online help. I access the help files by using the Get-Help cmdlet (help is an alias for that cmdlet), as seen here:

PS C:\> help about_remote*

Name                              Category  Synopsis
----                              --------  --------
about_remote                      HelpFile  Describes how to run remote commands in Windows PowerShell.
about_remote_FAQ                  HelpFile  Contains questions and answers about running remote commands
about_remote_jobs                 HelpFile  Describes how to run background jobs on remote computers.
about_remote_output               HelpFile  Describes how to interpret and format the output of remote commands.
about_remote_requirements         HelpFile  Describes the system requirements and configuration requirements for
about_remote_troubleshooting      HelpFile  Describes how to troubleshoot remote operations in Windows PowerShell.

PS C:\>

To run a command on a remote machine, you can use the Invoke-Command cmdlet, as shown here:

PS C:\> Invoke-Command -ComputerName win7-pc -ScriptBlock { ipconfig }

Windows IP Configuration


Ethernet adapter Local Area Connection 2:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::dc70:f882:bda5:9133%17
   IPv4 Address. . . . . . . . . . . : 192.168.1.110
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.254

Tunnel adapter isatap.{468C0E87-CED5-4D23-9F0E-C1B99C508131}:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :

Tunnel adapter Local Area Connection* 11:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :
PS C:\>

Executing against multiple machines is just as easy. If you have several computer names, you may want to place them into a text file that is readily accessible. This file needs to be accessible from the computer from where you launch the script—it does not need to be accessible on the target computers. A simple computers.txt file is seen in the following image.

Image of computers.txt file

By using the Get-Content cmdlet to read the contents of the text file to obtain the values for the –computername parameter, you can easily execute the command on a group of remote servers. This is seen here:

PS C:\> Invoke-Command -ComputerName (Get-Content C:\fso\comptuers.txt) -ScriptBlock {ipconfig}

Windows IP Configuration


Ethernet adapter Local Area Connection 2:

   Connection-specific DNS Suffix  . :
   Link-local IPv6 Address . . . . . : fe80::dc70:f882:bda5:9133%17
   IPv4 Address. . . . . . . . . . . : 192.168.1.110
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.254

Tunnel adapter isatap.{468C0E87-CED5-4D23-9F0E-C1B99C508131}:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :

Tunnel adapter Local Area Connection* 11:

   Media State . . . . . . . . . . . : Media disconnected
   Connection-specific DNS Suffix  . :

Windows IP Configuration


Ethernet adapter Local Area Connection 3:

   Connection-specific DNS Suffix  . : nwtraders.com
   Link-local IPv6 Address . . . . . : fe80::ec91:63e2:e2c9:3692%414
   IPv4 Address. . . . . . . . . . . : 192.168.1.100
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.254
PS C:\>

If for some reason you cannot upgrade to Windows PowerShell 2.0 at this time, your options will be somewhat limited. You may be able to use WMI and the create method from win32_process to run the remote command. The syntax is a bit nasty, as seen here:

 ([wmiclass]"\\COMPUTERNAME\root\cimv2:win32_process").create("PROCESSNAME")


 

Troubleshooting a Windows PowerShell Script for Active Directory 

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I’ve tried to use Windows Powershell 2.0’s Active Directory cmdlets to create a list of orphaned home drives as per the below; however, it seems that the Get-ADUser query always returns $null for all users with a samaccountname starting with 0-9 or A-F, even though doing a “Get-ADUser ‘username’ –Properties HomeDirectory” for one of those users returns a non-null value. Anyone with a samaccountname starting with G-Z returns the correct value; this happens whether it is run as part of this script or as one line against one of the accounts. Is there some quirk that I am unaware of or is there something seriously wrong with our domain? If it is relevant, our domain is 2008 Native with a mix of 2008 and 2008 R2 domain controllers, both RW and RO.

import-module ActiveDirectory
 
$path = "
\\path\to\users\"
$file = New-Item -type file "C:\unusedhome.txt"
 
 
$homedrives = Get-ChildItem $path
 
ForEach($folder in $homedrives){
 
$name = $path + $
folder.name
 
$user = Get-ADUser -Filter { HomeDirectory -eq $name }
 
if($user -eq $null){
add-content $file $name
}
 
$user = $null
 
}

Many thanks.

-- AB

 

Hey, Scripting Guy! AnswerHello AB,

Microsoft MVP Brandon answered this one. He said, “Try this, it works for me” about this script, which he wrote:

import-module ActiveDirectory

$path = "C:\data\HomeFolder\"
$file = New-Item -type file "C:\data\unusedhome.txt" -Force
 
$homedrives = Get-ChildItem $path
 
foreach($folder in $homedrives)
{
      $user = Get-ADUser $Folder.Name  -properties HomeDirectory
      Write-Host "Found User: $User"
      if($user.homedirectory -ne $folder.FullName){$folder.FullName | add-content $file.FullName}
      $user = $null
}


 

Can I Detect the Title of the Command Prompt Window?

Hey, Scripting Guy! Question

Hey, Scripting Guy!

I want to detect the command prompt window title. I need to be able to determine if the user is running in an elevated/admin prompt or not. The window title for a "normal" Command Prompt windows says, "Command Prompt," but the window title for an admin Command Prompt windows says, "Administrator: Command Prompt."

If I store the title so I can compare against the two window titles above, I can determine if I am just in a normal shell or an elevated shell and then take actions based on where the user is. I was hoping to use ordinary command-prompt utilities, but I do know VBScript, I have not looked very deep into Windows PowerShell yet.

However, you could restate the task as "determining if a user is in an elevated prompt." It does not matter to me if the window title is used to figure that out. It is just the only way I can see at the moment that they are different. If there is, another way to do this is okay, too.

Hope you can help.

-- KW

 

Hey, Scripting Guy! AnswerHello KW,

Take a look at this script. It uses Windows PowerShell 2.0 (required for the TryCatch bit).

Test-CmdPromptAdmin.ps1

# --------------------------------------------------------------------
# Test-CmdPromptAdmin.ps1
# ed wilson, msft, 12/22/2009
#
# Checks to see if the cmd prompt is elevated.
# If no cmd prompt then script exits.
#
# --------------------------------------------------------------------
Function Test-CmdPromptAdmin
{
 $errorActionPreference = "stop"
 Try
  {
   $windowTitle = (get-Process -Name cmd).MainWindowTitle
   if($windowTitle -match "administrator")
     { $true }
   else { $false }
  }
 Catch [System.Exception]
  { exit }
} #end function Test-CmdPromptAdmin

# *** Entry point to script ***

If(Test-CmdPromptAdmin)
  { "The cmd prompt is elevated"}
If(-not (Test-CmdPromptAdmin))
  { "The cmd prompt is not elevated" }


 

How Can I Add Quotation Marks Around a String Variable?

Hey, Scripting Guy! Question

Hey, Scripting Guy! 

Hey, Scripting Guy! I would like to add quotation marks around a string variable. However, for whatever reason, the first quotation mark is fine but the second quotation mark goes to the next line. Any ideas about how to fix this? Here is all I am doing:

PS C:\> $test='"'+"$strname"+'"'

PS C:\> $test

"\\?\Volume{747b409b-b755-11de-ab54-0025b3a915b7}\

   "

As you can see, the second quotation mark is not where I want it to be. I want it to be after the last "\".

-- OC

 

Hey, Scripting Guy! AnswerHello OC,

My guess is that it is exceeding your screen width. The script should still run fine.

In the Windows PowerShell console, you can increase the screen width by clicking the Windows Powershell icon in the upper left corner, clicking Properties, and then clicking Layout. The width is by default set to 80; I generally set mine to 120. This is seen in the following image.

Image of increasing screen width

 

Well, this concludes another edition of Quick-Hits Friday. It also concludes another exciting week on the Script Center. Join us next week as we delve into the mysteries of…well, we will let that remain a mystery for now.

If you want to know exactly what we will be covering on Monday, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you on Monday. Until then, have an awesome weekend.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Hey, Scripting Guy! How Can I Create Managed Service Accounts?

 Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! The pointy-headed boss is on a rampage. The problem is he has no idea why he is rampaging. This guy makes Nick Bottom seem like Aristotle. His latest obsession concerns service accounts. He wants me to create user accounts, with names like SQL_Service_Account, make these accounts Domain Administrators, and set their passwords so that they do not expire. I have tried telling him that such an approach is so last century, but he will not listen. I know this may not be a scripting question, but he respects you guyseven if he will not listen to his own staff. Can you help?

-- KM

 

Hey, Scripting Guy! AnswerHello KM,

Microsoft Scripting Guy Ed Wilson here. I love the smell of the bergamot oil extract that is present in Earl Grey tea. A fresh pot of Earl Grey tea and an ANZAC biscuit and I am ready to swim with the sharks—quite literally as shown in the following image, which shows a shark I snapped when I was diving in Australia.

Image of a shark friend of Ed's

 

KM, believe it or not, your question is a scripting question. Windows Server 2008 R2 introduces the concept of the managed service account. Managed service accounts provide a couple of benefits. Perhaps the most important benefit is you do not need to mess with resetting the passwords manually because passwords for managed service accounts are reset automatically. This provides the security benefit of new passwords with the advantage of no administrative overhead. The reason this becomes a scripting question is that there is no ability in Active Directory Users and Computers (that I have seen) to create a new service account. You will need to use Windows PowerShell.

The first thing you will need to do is load the ActiveDirectory module.

For more information on the ActiveDirectory module, see Monday’s Hey, Scripting Guy! post.

To import the activedirectory module, use the Import-Module cmdlet and supply the name of the module. You do not need to type the entire module name because you can use wildcard characters if you wish. The only requirement is that the wildcard character combination match only one module. This is shown here:

PS C:\> Import-Module active*

To create a new Active Directory Service Account, use the New-ADServiceAccount cmdlet. One parameter is required: the name of the service account to be created. The default location in Active Directory for managed service accounts is the Managed Service Account container. The following example creates a new managed service account named sql-srv1 in the managed service accounts container in the NWTraders domain:

PS C:\> New-ADServiceAccount -Name sql-srv1 -Path "cn=managed service accounts,dc=nwtraders,dc=com"

After the command has executed, the new service account appears, as seen in the following image.

Image of the new service account appearing after command executes

 

The previous command is kind of long, and you may wish to shorten it. The first task in shortening the length of the command is to see if there is an alias for the New-ADServiceAccount. Using the Get-Alias cmdlet, you get the following results:

PS C:\> Get-Alias -Definition New-ADServiceAccount

Get-Alias : This command cannot find a matching alias because alias with definition 'New-ADServiceAccount' do not exist

.

At line:1 char:10

+ Get-Alias <<<<  -Definition New-ADServiceAccount

    + CategoryInfo          : ObjectNotFound: (New-ADServiceAccount:String) [Get-Alias], ItemNotFoundException

    + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand

 

It appears that there is no alias for the New-ADServiceAccount. This is curious, so you use the Get-Alias cmdlet to see if there are any aliases for the Active Directory cmdlets. By using a wildcard character for the cmdlet definition, you can pick up aliases for cmdlets that contain the letters ad in their name, as shown here:

PS C:\> Get-Alias -Definition *ad*

 

CommandType     Name                                                Definition

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

Alias           ac                                                  Add-Content

Alias           AddComputer                                         Add-Computer

Alias           AddContent                                          Add-Content

Alias           AddHistory                                          Add-History

Alias           AddMember                                           Add-Member

Alias           AddPSSnapin                                         Add-PSSnapin

Alias           AddType                                             Add-Type

Alias           asnp                                                Add-PSSnapIn

Alias           ReadHost                                            Read-Host

There are no aliases created for the Active Directory cmdlets. However, this should not prevent you from creating one if you wish to do so. You could put the aliases into a module that you load when you load the Active Directory cmdlets.

To create a new alias, use the New-Alias cmdlet. After the alias is created, you can use the alias to create a new service account:

PS C:\> New-Alias -Name adsa -Value New-ADServiceAccount

PS C:\> adsa test1

PS C:\>

You do not need to specify the location for the managed service account if it is to be placed in the default location. The new managed service account is shown in the following image.

Image of new managed service account

 

You can specify the password, the location, and even the enabled parameter when creating a new Active Directory service account. One thing to keep in mind is that the password must be a secure string. One way to create a secure string is to use the ConvertTo-SecureString cmdlet. If you type in a plain text string to the ConvertTo-SecureString cmdlet, you need to specify that it is plain text by using the –asplaintext parameter and then you need to use the –force parameter to tell the cmdlet you know what you are doing, and you really do want to use the plain text string as a secure string. By default, service accounts are created with a 240-character randomly generated password. It is possible that the password you specify will not meet complexity requirements. The account will still be created, but it will not be enabled until you have set a password that meets complexity requirements. The following command creates a new service account named sql-srv2. It is a single command that will probably wrap in your Windows PowerShell console, unless you have a widescreen monitor and have increased the dimensions of your Windows PowerShell console.

PS C:\> New-ADServiceAccount -Name sql-srv2 -Path "cn=managed service accounts,dc=nwtraders,dc=com" -accountPassword (ConvertTo-SecureString -AsPlainText "P@ssword1" -Force) -enabled $True

If you wish the service account to be trusted for Kerberos delegation, use the –TrustedForDelegation parameter and set its value to $true. When a service account is trusted for delegation, it is permitted to assume the identity of a client requesting the service. The single line of code to create a trusted service account is seen here:

PS C:\> New-ADServiceAccount -Name sql-srv3 -Path "cn=managed service accounts,dc=nwtraders,dc=com" -accountPassword (ConvertTo-SecureString -AsPlainText "P@ssword1" -Force) -enabled $True -TrustedForDelegation $true

As a best practice, I recommend supplying a description for any service account that you create. This will help you to know why the account was created, what it is to be used for, and when it can be safely deleted. To specify a description, use the description property:

PS C:\> New-ADServiceAccount -Name sql-srv4 -Path "cn=managed service accounts,dc=nwtraders,dc=com" -accountPassword (ConvertTo-SecureString -AsPlainText "P@ssword1" -Force) -enabled $True -TrustedForDelegation $true -description "trusted SQL account"

PS C:\>

After all of the accounts have been created, you can use Active Directory Users and Computers to view the newly created accounts. This is seen in the following image.

Image of using Active Directory Users and Computers to view newly created accounts

 

KM, that is all there is to using the Active Directory cmdlets to create a managed service account. This also concludes Active Directory Week. Join us tomorrow for Quick-Hits Friday.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Dandelions, VCR Clocks, and Last Logon Times: These Are a Few of Our Least Favorite Things

 

(Note: This blog post appeared originally as an article on the old Microsoft Script Center. It was originally published in December 2005. We are resurrecting it here by popular demand.)


Sometimes the things that should be so easy turn out to be incredibly difficult. For example, have you ever tried to get rid of dandelions? (One of the Scripting Guys once found a dandelion growing on the roof of his house.) How about getting your VCR clock to stop flashing 12:00 over and over again? (It’s been estimated that as many as 25% of all the VCRs in use are even now flashing 12:00.) And as one Scripting Guy discovered, it’s actually easier to build a new Volkswagen Passat from scratch than it is to change the headlight.

The same thing has always been true of a seemingly-innocuous Active Directory task: determining the last time a user logged on to the domain. Determining the last logon time ought to be pretty easy; after all, Active Directory includes an attribute – lastLogon – that tells you the last time a user or computer logged on. How hard could it be simply to retrieve and report the value of that one attribute?

Well, as it turns out, surprisingly hard. For one thing, there’s some difficulty regarding the way the last logon date and time are stored in Active Directory. But that can be solved with some clever coding and mathematics. A much bigger stumbling block is this: the lastLogon attribute is not replicated from one domain controller to another. Suppose a new user logs on to domain controller A. You now write a script that requests the last logon time for our new user, and the script happens to connect to domain controller B. Oddly enough, the script will tell you that the user has never logged on, even though you know for a fact that the user is logged on right now.


 

Hey, What’s Going on Here?

So why is Active Directory lying to you? Well, it’s not really lying, it’s just that domain controller B doesn’t know that the user logged on to domain controller A. Because the lastLogon attribute is not replicated throughout the domain, if our new user has never logged on to domain controller B then domain controller B will have no knowledge of the user’s last logon time. In fact, to determine the last logon time for a user you have to retrieve the lastLogon attribute from every domain controller in the domain and then compare all those values to determine the true last logon time.

Yuck.

The Scripting Guys, who are asked several times a day how to determine the last time a user logged on to a domain (Mom, please, quit asking: you don’t want to know!), have just one thing to say: thank goodness for Windows Server 2003. The lastLogon attribute is still present in the Active Directory schema for Windows 2003 and this attribute still isn’t replicated from one domain controller to another. But that’s OK, because there’s a brand-new attribute in the schema: lastLogonTimestamp. This attribute also keeps track of the last time a user logged on to the domain, but – wonder of wonders – this new attribute is replicated from one domain controller to another. Want to know the last time a user logged on? Then just write a script and connect to any domain controller; the value will be the same on each one.

It is like a miracle, isn’t it?

In this article we’ll show you a script that can return the last logon time for a user in a Windows Server 2003 domain. Before we do this, however, bear in mind that we still face a few complicating factors. For one thing, it’s important to note that the last logon timestamp will typically not report the user’s true last logon time. Why not? Well, imagine a group of users who log on and log off several times a day. Each time one of these users logs on that information would have to be replicated throughout the entire domain. That could generate a large amount of replication traffic, and for little purpose: typically you care about only the so-called “stale” accounts,” users who haven’t logged on in the last few weeks. For the most part, you don’t need an up-to-the-minute report on each user’s last logon status. Because of that, the lastLogonTimestamp is replicated only once every 14 days. This helps limit replication traffic, although it also means that the lastLogonTimestamp for any given user could be off by as much as 14 days.

Note. If that actually is a problem then you can simply connect to each domain controller and retrieve the value of the lastLogon attribute for the user. The lastLogon attribute isn’t replicated throughout the domain, but it is updated on the authenticating domain controller each time a user logs on. But if you’re trying to answer a question like “Do we have any users who haven’t logged on in the past two weeks?” then the lastLogonTimestamp will more than suffice.

Believe it or not, the fact that the lastLogonTimestamp isn’t 100% accurate actually makes our script a little easier to write. As you’ll see, we have to go through some mathematical gyrations in order to convert the lastLogonTimestamp to a date-time value we can make sense of. If we had to adjust for possible time zone differences between our computer and the domain controllers that would make our math even more complicated. But we don’t really have to worry about that. After all, we already know – in advance – that our last logon time could be off by as much as 14 days. Based on that, there’s no reason to worry about a few hours’ worth of time zone differences.

The other complicating factor, as we hinted at, is this: the lastLogonTimestamp is stored as a 64-bit integer. When you query the lastLogonTimestamp you don’t get back a date-time like May 15, 2005 8:05 AM. Instead, you get back the number of 100-nanosecond intervals that passed between January 1, 1601 and the time the user last logged on. (Come on: we’re not clever enough to make up something like that!) Consequently most of our code will be involved in taking that weird 64-bit integer value and converting it to a date and time.

Note. In case you’re wondering, a number of years ago the American National Standards Institute (ANSI) adopted a system of counting days; this system began with December 31, 1600 as Day 0. In turn, that made January 1, 1601 the first “official” day in history, with all subsequent dates and times being based on the number of nanoseconds elapsed since the 0 hour on January 1, 1601. (That day was a Monday, by the way.) These so-called ANSI decimal dates were originally designed for use with the COBOL programming language and have continued to be used by Windows and other operating systems.

Incidentally, did we mention the fact that VBScript can’t actually handle the 64-bit integer returned by lastLogonTimestamp? Well, we should have: 64-bit integers are not supported in VBScript. But at least there is a workaround for this: ADSI’s IADsLargeInterger interface can break this into a pair of 32-bit integers for us, and VBScript can handle those two integers just fine. That means we can still work with the lastLogonTimestamp attribute: we just need to use an additional step, one in which we add the two 32-bit integers to get a single value that VBScript is comfortable with.


Don’t Worry: Everything’s Going to Be Just Fine

Still with us? (We were afraid we’d scared everyone off.) Despite all those dire warnings the script that returns the last logon time for the user really isn’t all that bad. Here, see for yourself:

Set objUser = GetObject("LDAP://cn=Ken Myer, ou=Finance, dc=fabrikam, dc=com")

Set objLastLogon = objUser.Get("lastLogonTimestamp")

 

intLastLogonTime = objLastLogon.HighPart * (2^32) + objLastLogon.LowPart

intLastLogonTime = intLastLogonTime / (60 * 10000000)

intLastLogonTime = intLastLogonTime / 1440

 

Wscript.Echo "Last logon time: " & intLastLogonTime + #1/1/1601#

The script starts off easy enough: we simply bind to the user account in Active Directory and then use the Get method to retrieve the lastLogonTimestamp, storing that value in an IADsLargeInteger object with the object reference objLastLogon.

Note. One of the nice things about ADSI is that, in general, we don’t have to tell it which interface to use; you might notice that we never create an instance of the IADsLargeInteger object. Instead ADSI typically figures that sort of thing out for itself. You can see we also never explicitly tell it that we’re working with a user object. ADSI is smart enough to determine that without any help.

This is where things get a tad bit hairy. The IADsLargeInteger object has two properties: HighPart, which stores the upper 32 bits of our 64-bit integer; and LowPart, which stores the lower 32 bits of the integer. To combine those into a single value we use this line of code:

intLastLogonTime = objLastLogon.HighPart * (2^32) + objLastLogon.LowPart

Don’t worry too much about the math; we’re just taking the HighPart times two to the 32nd power, and then adding the LowPart. Unless you’re a glutton for mathematical punishment just take it on faith that this formula is correct.

Believe it or not, that one line of code actually gives us the last logon time for the user; the only problem is that the last logon time comes back as the number of 100-nanosecond intervals that expired between January 1, 1601 and the user’s last logon. That’s going to be a value similar to this:

1.27588712492538E+17

How…nice….

What we need to do, obviously, is convert that value to something a little easier to deal with. As everyone knows, there are 1,000,000,000 nanoseconds in a second; therefore, there are 10,000,000 100-nanosecond intervals in a single second (10,000,000 x 100 = 1,000,000,000). We need to know that because – as we noted earlier – the lastLogonTimestamp measures time in 100-nanonsecond intervals. If we carry out the math one step further that also means there are 600,000,000 of these 100-nanonsecond intervals in each minute.

Don’t worry about it; we’ll wait until your head stops spinning. Better? OK, let’s proceed. Where are we going with this? Well, armed with this knowledge we can now use this line of code to tell us how many minutes elapsed between January 1, 1601 and the time the user last logged on (note that we’re taking 60 seconds times the 10,000,000 100-nanosecond intervals in each of those seconds):

intLastLogonTime = intLastLogonTime / (60 * 10000000)

And because there are 1,440 minutes in every 24-hour day, this line of code tells us how many days have elapsed:

intLastLogonTime = intLastLogonTime / 1440

And, yes, we could have used just one equation rather than two. We just thought that breaking it into two pieces would make it a little easier for you to follow.

As soon as we know the number of days that passed we can add that number to the date January 1, 1601 and generate a date-time value that makes some sense. (For example, suppose we determined 3 days had passed. We’d add 3 to January 1, 1601 and come up with a last logon time of January 4, 1601.) Here’s the code that does this addition:

Wscript.Echo "Last logon time: " & intLastLogonTime + #1/1/1601#

And here’s an example of the output we get:

Last logon time: 4/25/2005 2:54:09 PM

Definitely crazy. But, on the bright side, you can connect to any domain controller in the domain and retrieve this information. Like we said, in Windows 2000 you still have to do all this high-falutin’ mathematics; on top of that, though, you also have to connect to each and every domain controller, retrieve the value of the lastLogon attribute, and then compare all those values to determine the last time the user logged on. Compared to that, lastLogonTimestamp represents a huge leap forward. 

Now if Microsoft could just do something about those flashing 12:00s we’d be in business.

 

Hey, Scripting Guy! Can I Use Windows PowerShell to Manage AD DS Security Groups?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am a MCSE and I still remember my instructor at the training center running around the room shouting, “Users go into groups, and groups get assigned rights and permissions.” Over and over again in different manners and in different language, but always with the same intent, he kept repeating the same mantra. I could not get that phrase out of my head, which was a good thing when it came time to take the exam. My network is therefore organized in such a way that I make extensive use of security groups. My question is this: Is there a way I can use Windows PowerShell to create and manage security groups in Active Directory Domain Services (AD DS)?

-- DM

 

Hey, Scripting Guy! AnswerHello DM,

Microsoft Scripting Guy Ed Wilson here. I was up late last night reading Homer's Odyssey, and therefore I did not spring from my slumber with my normal Scripting Guy exuberance. I therefore added an extra scoop of English Breakfast tea leaves to my pot before heading up to the Scripting Guy command center. There were several cool tweets on Twitter that needed responses, and with Deep Purple cranked up so loud on my Zune HD that the Venetian blinds were actually rattling, I dove into the scripter@microsoft.com inbox.

Your mail brought back fond memories of Cincinnati where I used to teach the class for the Microsoft NT 4.0 Server in the Enterprise Exam 70-068. (I contributed to a study guide for that exam—one of my first projects as a writer.) Speaking of Cincinnati, I found the following picture of the Cincinnati Tyler Davidson Fountain that I took a few years ago when I was teaching a VBScript class at the Microsoft Office there.

Image of Tyler Davidson Fountain

 

DM, let us now get started. To create a new global security group, use the New-ADGroup Windows PowerShell AD DS cmdlet. The New-ADGroup Windows PowerShell cmdlet requires three parameters: the name of the group, a path to the location where the group will be stored, and the groupscope (global, universal, or domainlocal). Before running the command seen here, remember you must import the ActiveDirectory module into your current Windows PowerShell session. For more information about working with the ActiveDirectory module, see Monday’s Hey, Scripting Guy! Blog post.

New-ADGroup -Name hsgTestGroup -Path "ou=HSG_TestOU,dc=nwtraders,dc=com" -groupScope global

The newly created group is seen in the following image.

Image of newly created group

 

To create a new universal group, you only need to change the groupscope parameter value, as seen here.

New-ADGroup -Name hsgTestGroup1 -Path "ou=HSG_TestOU,dc=nwtraders,dc=com" -groupScope universal

The newly created universal group is seen in Active Directory Users and Computers, as shown in the following image.

Image of newly create universal group

 

To add a user to a group, you must supply values for the identity parameter and the members parameter. The value you use for the identity parameter is the name of the group. You do not need to use the LDAP syntax of cn=groupname. You need only to supply the name. In examining the LDAP attributes for a group in ADSI Edit, as seen in the following image, you can obtain the needed value from several fields.

Image of LDAP attributes for a group in ADSI Edit

 

It is a bit unusual that the -members parameter is named members and not member because most Windows PowerShell cmdlet parameter names are singular and not plural. The parameters are singular even when they accept an array of values (such as the computername parameter). The command to add a new group named hsgTestGroup1 to the hsgUserGroupTest group is seen here:

Add-ADGroupMember -Identity hsgTestGroup1 -Members hsgUserGroupTest

To remove a user from a group, use the Remove-ADGroupMember cmdlet with the name of the user and group. The identity and members parameters are required, but the command will not execute without confirmation, as seen here:

PS C:\> Remove-ADGroupMember -Identity hsgTestGroup1 -Members hsgUserGroupTest

Confirm
Are you sure you want to perform this action?
Performing operation "Set" on Target "CN=hsgTestGroup1,OU=HSG_TestOU,DC=NWTraders,DC=Com".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
PS C:\>

If you are sure that you wish to remove the user from the group and you wish to suppress the query, use the –confirm parameter and assign the value $false to it. The problem is you will need to supply a colon between the parameter and $false value.

The use of the colon before the –confirm parameter is not documented, and took me more than two hours of experimentation to figure out. I also did extensive searches on Bing and was unable to find anything.

The command is seen here:

Remove-ADGroupMember -Identity hsgTestGroup1 -Members hsgUserGroupTest -Confirm:$false

You need the ability to suppress the confirmation prompt to be able to use the Remove-ADGroupMember cmdlet in a script. The first thing the RemoveUserFromGroup.ps1 script does is load the activedirectory module. After the module is loaded, the Remove-ADGroupMember cmdlet is used to remove the user from the group. To suppress the confirmation prompt, the –confirm:$false command is used. The RemoveUserFromGroup.ps1 script is seen here.

RemoveUserFromGroup.ps1

import-module activedirectory

Remove-ADGroupMember -Identity hsgTestGroup1 -Members hsgUserGroupTest -Confirm:$false

DM, that is all there is to working with Groups in Active Directory. Active Directory Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or FaceBook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys 

 

Hey, Scripting Guy! How Can I Create Users and Organizational Units with Active Directory Domain Services Cmdlets?

Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I am interested in using Active Directory Domain Services (AD DS) cmdlets to create users and organizational units. Is this a hard thing to do? I only ask this because I am an extremely busy network administrator who has barely had time to begin looking at Windows PowerShell. I do not have a lot of time to spend learning a bunch of cryptic commands. If it is really hard, I will need to take a pass on the information right now.

-- DA

 

Hey, Scripting Guy! AnswerHello DA,

Microsoft Scripting Guy Ed Wilson here. I have thirty minutes before my one-on-one meeting with the Scripting Manager. Having a one-on-one with Steve is always the highlight of my day because he is funny, intelligent, and extremely passionate about the TechNet Script Center. I always emerge from our meetings with lots of ideas for new things to try. As an added bonus, Steve is also a writer who has written several certification books; therefore, he understands the writing process in a very real and personal manner. Before my meeting with Steve, I thought I would check the e-mail in the scripter@microsoft.com inbox, send out a few tweets on Twitter, make a fresh pot of Earl Gray tea, and crank up Pink Floyd on my Zune HD.

Now that I am mentally and physically set for my one-on-one, let me get prepared for the meeting. I know he will ask about the 2010 Scripting Games. So how are we doing there?

·         I spent much of yesterday creating three events for the 2010 Scripting Games.

·         We have a theme picked out: The Winter Olympics.

·         We have 10 event themes selected.

He will also ask about the new Weekend Scripter series.

·         Weekend Scripter starts on Saturday February 6, 2010, when we begin publishing seven days a week. (See? We really do love this stuff.)

·         The first Weekend Scripter blog post will be about writing a Windows PowerShell function to determine the volume of a space.

·         The second Weekend Scripter blog post (February 7, 2010) will be about figuring the center of a circle.

He will also ask me about how we are doing with Twitter. What can I say about Twitter?

·         We have been meeting some really passionate Windows PowerShell people on Twitter.

·         I have been invited to speak to one Windows PowerShell users group via Twitter.

·         I have found several moderators for The Official Scripting Guys Forum via Twitter.

·         I have answered dozens of questions from various users via Twitter.

·         I have received some really cool ideas for the 2010 Scripting Games via Twitter.

Cool, I am all set for my one-on-one. I still have some more time. Let me look through some of my pictures. I have been trying to collect all of my digital pictures into a single location in order to get a good backup of them. It is somewhat of a challenge when you own a dozen computers. I have 7 computers in my office right now. The rest of the computers are spread throughout the house. Unfortunately, through the years I have been very sloppy about organizing my digital pictures. One day I will write some scripts to help me with the pictures. Here is one cool picture I took during the time I was teaching a WMI class in Munich, Germany. One Saturday, I took the train to Salzburg, Austria. I took the following picture that day.

Image of Salzburg, Austria

 

DA, I am back from my one-on-one with the Scripting Manager. You will be glad to know that using the AD DS Windows PowerShell cmdlets can be extremely easy. For example, to create an organizational unit (OU) in Active Directory, all you need to do is to supply the name and the path. The behavior is exactly the same as creating a new user in Active Directory, which we discussed yesterday.

To create a new OU, use the New-ADOrganizationalUnit cmdlet:

New-ADOrganizationalUnit -Name HSG_TestOU -Path "dc=nwtraders,dc=com"

If you wish to create a child OU, you use the New-ADOrganizationalUnit cmdlet, but in the path, you list the location that will serve as the parent:

New-ADOrganizationalUnit -Name HSG_TestOU1 -Path "ou=HSG_TestOU,dc=nwtraders,dc=com"

If you wish to make several child OUs in the same location, press the up arrow to retrieve the previous command and edit the name of the child. You can press the HOME key to move to the beginning of the line, the END key to move to the end of the line, and the left and right arrow keys to quickly find your place on the line so you can edit it. A second child OU is created here:

New-ADOrganizationalUnit -Name HSG_TestOU2 -Path "ou=HSG_TestOU,dc=nwtraders,dc=com"

The newly created OUs are seen in the following image.

Image of newly created OUs

 

To create a computer account in one of the newly created child OUs, you must type the complete path to the OU that will house the new computer account. The New-ADComputer cmdlet is used to create new computer accounts in AD DS. In this example, the HSG_TestOU1 OU is a child of the HSG_TestOU OU; therefore, both OUs must appear in the path parameter. Keep in mind that the path that is supplied to the path parameter must be contained inside quotation marks, as seen here:

New-ADComputer -Name HSG_Test -Path "ou=HSG_TestOU1,ou=HSG_TestOU,dc=nwtraders,dc=com"

To create a user account, use the New-ADUser cmdlet, as seen here:

New-ADUser -Name HSG_TestChild -Path "ou=HSG_TestOU1,ou=HSG_TestOU,dc=nwtraders,dc=com"

Because there could be some redundant typing involved, you may wish to write a script to create the OUs at the same time the computer accounts and user accounts are created. A sample script that creates OUs, users, and computers is the UseADCmdletsToCreateOuComputerAndUser.ps1 script seen here.

UseADCmdletsToCreateOuComputerAndUser.ps1

Import-Module -Name ActiveDirectory
$Name = "HSG_ScriptTest"
$DomainName = "dc=nwtraders,dc=com"
$OUPath = "ou={0},{1}" -f $Name, $DomainName

New-ADOrganizationalUnit -Name $Name -Path $DomainName -ProtectedFromAccidentalDeletion $false

For($i = 0; $i -le 5; $i++)
{
 New-ADOrganizationalUnit -Name $Name$i -Path $OUPath -ProtectedFromAccidentalDeletion $false
}

For($i = 0 ; $i -le 5; $i++)
{
 New-ADComputer -Name  "HSGTestComputer$i" -Path $OUPath
 New-ADUser -Name "HSGTestUser$i" -Path $OUPath
}

The UseADCmdletsToCreateOuComputerAndUser.ps1 script begins by importing the ActiveDirectory module. It then creates the first OU. When testing a script, it is important to disable the deletion protection by using the –ProtectedFromAccidentalDeletion parameter. This will allow you to easily delete the OU and avoid having to go into the advanced view in Active Directory Users and Computers and changing the protected status on each OU.

After the HSG_ScriptTest OU is created, the other OUs, users, and computer accounts can be created inside the new location. It seems obvious that you cannot create a child OU inside the parent OU if the parent has not yet been created, but it is easy to make a logic error like this.

 

DA, that is all there is to using the Active Directory Domain Services Windows PowerShell cmdlets to create new OUs, computer accounts, and user accounts. Active Directory Week will continue tomorrow.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

More Posts Next page »
 
Page view tracker