Chapter 1.The PowerShell Console Welcome to PowerShell! This chapter will introduce you to the PowerShell console and show you how to configure it, including font colors and sizes, editing and display options. Topics Covered: Starting PowerShell First Steps with the Console o Incomplete and Multi-Line Entries o Important Keyboard Shortcuts o Deleting Incorrect Entries o Overtype Mode o Command History: Reusing Entered Commands o Automatically Completing Input o Scrolling Console Contents o Selecting and Inserting Text o QuickEdit Mode o Standard Mode Customizing the Console o Opening Console Properties o Defining Options o Specifying Fonts and Font Sizes o Setting Window and Buffer Size o Selecting Colors o Directly Assigning Modifications in PowerShell o Saving Changes Piping and Routing o Piping: Outputting Information Page by Page o Redirecting: Storing Information in Files Summary Starting PowerShell On Windows 7 and Server 2008 R2, Windows PowerShell is installed by default. To use PowerShell on older systems, you need to download and install it. The update is free. The simplest way to find the appropriate download is to visit an Internet search engine and search for "KB968930 Windows XP" (replace the operating system with the one you use). Make sure you pick the correct update. It needs to match your operating system language and architecture (32-bit vs. 64-bit). After you installed PowerShell, you'll find PowerShell in the Accessories program group. Open this program group, click on Windows PowerShell and then launch the PowerShell executable. On 64-bit systems, you will also find a version marked as (x86) so you can run PowerShell both in the default 64-bit environment and in an extra 32-bit environment for backwards compatibility. You can also start PowerShell directly. Just press (Windows)+(R) to open the Run window and then enter powershell (Enter). If you use PowerShell often, you should open the program folder for Windows PowerShell and right-click on Windows PowerShell. That will give you several options: Add to the start menu: On the context menu, click on Pin to Start Menu so that PowerShell will be displayed directly on your start menu from now on and you won't need to open its program folder first. Quick Launch toolbar: Click Add to Quick Launch toolbar if you use Windows Vista and would like to see PowerShell right on the Quick Launch toolbar inside your taskbar. Windows XP lacks this command so XP users will have to add PowerShell to the Quick Launch toolbar manually. Jump List: On Windows 7, after launching PowerShell, you can right-click the PowerShell icon in your taskbar and choose Pin to Taskbar. This will not only keep the PowerShell icon in your taskbar so you can later easily launch PowerShell. It also gives access to its new "Jump List": right-click the icon (or pull it upwards with your mouse). The jump list contains a number of useful PowerShell functions: you can launch PowerShell with full administrator privileges, run the PowerShell ISE, or open the PowerShell help file. By the way: drag the pinned icon all to the left in your taskbar. Now, pressing WIN+1 will always launch PowerShell. And here are two more tips: hold SHIFT while clicking the PowerShell icon in your taskbar will open a new instance, so you can open more than one PowerShell console. Holding SHIFT+CTRL while clicking the PowerShell icon opens the PowerShell console with full Administrator privileges (provided User Account Control is enabled on your system). Keyboard shortcuts: Administrators particularly prefer using a keyboard instead of a mouse. If you select Properties on the context menu, you can specify a key combination in the hot-key field. Just click on this field and press the key combination intended to start PowerShell, such as (Alt)+(P). In the properties window, you also have the option of setting the default window size to start PowerShell in a normal, minimized, or maximized window. Figure 1.1: How to always open PowerShell with administrator rights (Run without administrative privileges whenever possible) First Steps with the Console After PowerShell starts, its console window opens, and you see a blinking text prompt, asking for your input with no icons or menus. PowerShell is a command console and almost entirely operated via keyboard input. The prompt begins with "PS" and after it is the path name of the directory where you are located. Start by trying out a few commands. For example, type: hello (Enter) As soon as you press (Enter), your entry will be sent to PowerShell. Because PowerShell has never heard of the command "hello" you will be confronted with an error message highlighted in red. Figure 1.2: First commands in the PowerShell console For example, if you'd like to see which files and folders are in your current directory, then type dir (Enter). You'll get a text listing of all the files in the directory. PowerShell's communication with you is always text-based. PowerShell can do much more than display simple directory lists. You can just as easily list all running processes or all installed hotfixes: Just pick a different command as the next one provides a list of all running processes: Get-Process (Enter) Get-Hotfix (Enter) PowerShell's advantage is its tremendous flexibility since it allows you to control and display nearly all the information and operations on your computer. The command cls deletes the contents of the console window and the exit command ends PowerShell. Incomplete and Multi-line Entries Whenever you enter something PowerShell cannot understand, you get a red error message, explaining what went wrong. However, if you enter something that isn't wrong but incomplete (like a string with one missing closing quote), PowerShell gives you a chance to complete your input. You then see a double-prompt (">>"), and once you completed the line and pressed ENTER twice, PowerShell executes the command. You can also bail out at any time and cancel the current command or input by pressing: (Ctrl)+(C). The "incomplete input" prompt will also appear when you enter an incomplete arithmetic problem like this one: 2 + (Enter) >> 6 (Enter) >> (Enter) 8 This feature enables you to make multi-line PowerShell entries: "This is my little multiline entry.(Enter) >> I'm now writing a text of several lines. (Enter) >> And I'll keep on writing until it's no longer fun."(Enter) >>(Enter) This is my little multiline entry. I'm now writing a text of several lines. And I'll keep on writing until it's no longer fun. The continuation prompt generally takes its cue from initial and terminal characters like open and closed brackets or quotation marks at both ends of a string. As long as the symmetry of these characters is incorrect, you'll continue to see the prompt. However, you can activate it even in other cases: dir `(Enter) >> -recurse(Enter) >>(Enter) So, if the last character of a line is what is called a "back-tick" character, the line will be continued. You can retrieve that special character by pressing (`). Important Keyboard Shortcuts Shortcuts are important since almost everything in PowerShell is keyboard-based. For example, by pressing the keys (Arrow left) and (Arrow right), you can move the blinking cursor to the left or right. Use it to go back and correct a typo. If you want to move the cursor word by word, hold down (Ctrl) while pressing the arrow keys. To place the cursor at the beginning of a line, hit (Home). Pressing (End) will send the cursor to the end of a line. If you haven't entered anything, then the cursor won't move since it will only move within entered text. There's one exception: if you've already entered a line and pressed (Enter) to execute the line, you can make this line appear again character-by-character by pressing (Arrow right). Deleting Incorrect Entries If you've mistyped something, press (Backspace) to delete the character to the left of the blinking cursor. (Del) erases the character to the right of the cursor. And you can use (Esc) to delete your entire current line. The hotkey (Ctrl)+(Home) works more selectively: it deletes all the characters at the current position up to the beginning of the line. Characters to the right of the current position (if there are any) remain intact. (Ctrl)+(End) does it the other way around and deletes everything from the current position up to the end of the line. Both combinations are useful only after you've pressed (Arrow left) to move the cursor to the middle of a line, specifically when text is both to the left and to the right of the cursor. Overtype Mode If you enter new characters and they overwrite existing characters, then you know you are in type-over mode. By pressing (Insert) you can switch between insert and type-over modes. The default input mode depends on the console settings you select. You'll learn more about console settings soon. Command History: Reusing Entered Commands The most awesome feature is a built-in search through all of the commands you used in your current session: simply type "#" and then some search text that you know exists in one or more of your previous commands. Next, type TAB one or more times to see all the commands that contained your keyword. Press ENTER to execute the command once you found it, or edit the command line to your liking. If you just wanted to polish or correct one of your most recent commands, press (Arrow up) to re-display the command that you entered. Press (Arrow up) and (Arrow down) to scroll up and down your command history. Using (F5) and (F8) do the same as the up and down arrow keys. This command history feature is extremely useful. Later, you'll learn how to configure the number of commands the console ―remembers‖. The default setting is the last 50 commands. You can display all the commands in your history by pressing (F7) and then scrolling up and down the list to select commands using (Arrow up) and (Arrow down) and (Enter). The numbers before the commands in the Command History list only denote the sequence number. You cannot enter a number to select the associated command. What you can do is move up and down the list by hitting the arrow keys. Simply press (F9) to ‗activate‘ the numbers so that you can select a command by its number. This opens a menu that accepts the numbers and returns the desired command. The keyboard sequence (Alt)+(F7) will clear the command history and start you off with a new list. (F8) provides more functionality than (Arrow up) as it doesn't just show the last command you entered, but keeps a record of the characters you've already typed in. If, for example, you'd like to see all the commands you've entered that begin with "d", type: d(F8) Press (F8) several times. Every time you press a key another command will be displayed from the command history provided that you've already typed in commands with an initial "d." Automatically Completing Input An especially important key is (Tab). It will save you a great deal of typing (and typing errors). When you press this key, PowerShell will attempt to complete your input automatically. For example, type: cd (Tab) The command cd changes the directory in which you are currently working. Put at least one space behind the command and then press (Tab). PowerShell suggests a sub-directory. Press (Tab) again to see other suggestions. If (Tab) doesn't come up with any suggestions, then there probably aren't any sub-directories available. This feature is called Tab-completion, which works in many places. For example, you just learned how to use the command Get-Process, which lists all running processes. If you want to know what other commands there are that begin with "Get-", then type: Get-(Tab) Just make sure that there's no space before the cursor when you press (Tab). Keep hitting (Tab) to see all the commands that begin with "Get-". A more complete review of the Tab-completion feature is available in Chapter 9. Tab-completion works really well with long path names that require a lot of typing. For example: c:\p(Tab) Every time you press (Tab), PowerShell will prompt you with a new directory or a new file that begins with "c:\p." So, the more characters you type, the fewer options there will be. In practice, you should type in at least four or five characters to reduce the number of suggestions. When the list of suggestions is long, it can take a second or two until PowerShell has compiled all the possible suggestions and displays the first one. Wildcards are allowed in path names. For example, if you enter c:\pr*e (Tab) in a typical Windows system, PowerShell will respond with "c:\Program Files". PowerShell will automatically put the entire response inside double quotation marks if the response contains whitespace characters. Scrolling Console Contents The visible part of your console depends on the size of your console window, which you can change with your mouse. Drag the window border while holding down your left mouse button until the window is the size you want. Note that the actual contents of the console, the "screen buffer," don't change. So, if the window is too small to show everything, you should use the scroll bars. Selecting and Inserting Text Use your mouse if you'd like to select text inside the PowerShell window and copy it onto the clipboard. Move the mouse pointer to the beginning of the selected text, hold down the left mouse button and drag it over the text area that you want to select. QuickEdit Mode QuickEdit is the default mode for selecting and copying text in PowerShell. Select the text using your mouse and PowerShell will highlight it. After you've selected the text, press (Enter) or right-click on the marked area. This will copy the selected text to the clipboard which you can now paste into other applications. To unselect press (Esc). You can also insert the text in your console at the blinking command line by right-clicking your mouse. Figure 1.3: Marking and copying text areas in QuickEdit mode Standard Mode If QuickEdit is turned off and you are in Standard mode, the simplest way to mark and copy text is to right-click in the console window. If QuickEdit is turned off, a context menu will open. Select Mark to mark text and Paste if you want to insert the marked text (or other text contents that you've copied to the clipboard) in the console. It's usually more practical to activate QuickEdit mode so that you won't have to use the context menu. Customizing the Console You can customize a variety of settings in the console including edit mode, screen buffer size, font colors, font sizes etc. Opening Console Properties The basic settings of your PowerShell console are configured in a special Properties dialog box. Click on the PowerShell icon on the far left of the title bar of the console window to open it. Figure 1.4: Opening console properties That will open a context menu. You should select Properties and a dialog box will open. To get help, click on the question mark button on the title bar of the window. A question mark is then pinned to your mouse pointer. Next, click on the option you need help for. The help appears as a ScreenTip window. Defining Options Under the heading Options are four panels of options: Figure 1.5: Defining the QuickEdit and Insert modes the new font will work only after you either log off at least once or restart your computer. If you fail to do so. In the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\ CurrentVersion\Console\TrueTypeFont insert a new "string value" and give this entry the name "00" (numbers. One reason for this is that the console always uses the same width for each character (fixed width fonts). Open your registry editor. This restricts the use of most Windows fonts because they're proportional typefaces: every character has its own width. Scalable TrueType fonts are much more flexible. The limited default font list is supposed to prevent you from choosing unsuitable fonts for your console.6: Specifying new fonts and font sizes You should also try experimenting with TrueType fonts by using the "bold fonts" option. just the way it's stated under the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts. TrueType fonts are often more readable if they're displayed in bold. you can choose any size in the size list or enter them as text in the text box. The "window" option is best so that you can switch to other windows when you're working. then call the new entry "000" or add as many zeroes as required to avoid conflicts with existing entries. If you're sure that a certain font will work in the console. Figure 1. TrueType fonts can be dynamically scaled. You should then double-click your new entry to open it and enter the name of the font. you can add them to the console font list. it will appear only once in the history list. We've already discussed the advantages of the QuickEdit mode: it makes it much easier to select. Your choice of fonts may at first seem a bit limited. When you select a TrueType font. This font is available in a specific range of sizes with available sizes shown in the "Size" list. Cursor size: Here is where you specify the size of the blinking cursor. Setting Window and Buffer Size . The name must be exactly the same as the official font name. then here's how to add the font to the console font list. If there's already an entry that has this name. So. Command history: Here you can choose how many command inputs the console ―remembers‖. The console often uses the raster font as its default. They're marked in the list by a "TT" symbol. The option Discard Old Duplicates ensures that the list doesn't have any duplicate entries. Display options: Determine whether the console should be displayed as a window or full screen. Specifying Fonts and Font Sizes On the Font tab. This allows you to select a command from the list by pressing (Arrow up) or (F7). not letters). copy. you can choose both the font and the font size displayed in the console. For example. Edit options: You should select the QuickEdit mode as well as the Insert mode. The Insert mode makes sure that new characters don't overwrite existing input so new characters will be added without erasing text you've already typed in when you're editing command lines. and insert text. if you enter one command twice. an "i" is narrower than an "m". the console will ignore your new font when you select it in the dialog box. The full screen display option is not available on all operating systems. To get more font choices. However. The newly added font will now turn up in the console's option field. Figure 1. Figure 1. You can read more about this in Chapter 10. If you don't like any of the 16 colors. You would have to include these lines into one of your "profile scripts" which run every time you launch PowerShell to make them permanent. Choose the option Let system position window and Windows will automatically determine at what location the console window will open. Once you close and re-open PowerShell. This gives you the opportunity to use the scroll bars to scroll the window contents back up so that you can look at all the results of your previous commands. if you want to specify a new font color. such as command history's (F7) Popup background: Popup window background color You have a palette of 16 colors for these four areas. then you can mix your own special shade of color. You'll hear more about this later.ui.rawui.ui. take a look at this: $host.7: Specifying the size of the window buffer You can also set the window size and position on this tab if you'd like your console to open at a certain size and screen position on your display.WindowTitle = "My Console" (Enter) These changes will only be temporary.rawui.ForegroundColor = "Yellow" (Enter) $host. meaning how much information the console should "remember" and how far back you can scroll with the scroll bars. green.On the Layout tab. Selecting Colors On the Colors tab. you can specify how large the screen buffer should be. and blue. the changes are gone. Saving Changes .rawui (Enter) $host. So. you should first select the option Screen Text and click on one of the 16 colors. Just click on a palette color and choose your desired color value at the upper right from the primary colors red.8: Select better colors for your console Directly Assigning Modifications in PowerShell Some of the console configuration can also be done from within PowerShell code.000 lines or larger. To give you a quick impression. You should select a width of at least 120 characters in the window buffer size area with the height should be at least 1. you can select your own colors for four areas: Screen text: Console font Screen background: Console background color Popup text: Popup window font.ui. you can close the dialog box. If you're using Windows Vista or above. PowerShell will look for it in all folders listed in the PATH environment variable. For example: . Redirecting: Storing Information in Files If you‘d like to redirect the result of a command to a file. all changes will be saved immediately. you'll see an additional window and a message asking you whether you want to save changes temporarily (Apply properties to current window only) or permanently (Modify shortcut that started this window). So to open a file. function. your new settings will already be in effect. you will have to specify its absolute or relative path name. operable program. For example. Piping and Routing You may want to view the information page by page or save it in a file since some commands output a lot of information. You can then open the file. opening a file in PowerShell is different from opening a file in the classic console: help. At line:1 character:8 + help.txt <<<< If you only specify the file name. Verify the term and try again. If you're using Windows XP. However. You may need Admin rights to save settings if you launched PowerShell with a link in your start menu that applies for all users. The results of the command to the left of the pipe symbol are then fed into the command on the right side of the pipe symbol. and when you start PowerShell the next time. Piping: Outputting Information Page by Page The pipe command more outputs information screen page by screen page. Name (Enter) You'll find more background information on piping as well as many useful examples in Chapter 5. or script file.txt (Enter) The term "help.txt (Enter) The information won't appear in the console but will instead be redirected to the specified file. you can use the redirection symbol ">": Help > help. You will need to press a button (like Space) to continue to the next page. not just more.txt" is not recognized as a cmdlet. This kind of piping is also known in PowerShell as the "pipeline": Get-Process | more (Enter) You can press (Ctrl)+(C) to stop output. Piping also works with other commands.Once you've successfully specified all your settings in the dialog box. Piping uses the vertical bar (|). if you‘d like to get a sorted directory listing. pipe the result to Sort -Object and specify the columns you would like to sort: dir | Sort-Object -Property Length. txt (Enter) Or.10.10..10.10: bytes=32 time<1ms TTL=128 Reply from 10.10.10: Packets: Sent = 4.10. This variable was introduced in PowerShell version 2.10: bytes=32 time<1ms TTL=128 Reply from 10.10. You will have to download and install PowerShell before using it. so if you find it on your system. Key Meaning . There is no reason why you should continue to use PowerShell 1.10. you can use Tab-completion and hit (Tab) after the file name: help. and the easiest way to find out whether you are using the most current PowerShell version is to launch the console and check the copyright statement. It not only tells you the current PowerShell version but also the versions of the core dependencies. output the automatic variable $psversiontable (simply by entering it). Average = 0ms Variables are universal data storage and variable names always start with a "$".0.10 $result Reply from 10. You'll find out more about variables in Chapter 3. Summary PowerShell is part of the operating system starting with Windows 7 and Server 2008 R2. to make it even simpler. then you are still using the old and outdated PowerShell 1. If you wanted to find out your current PowerShell version programmatically.0 it does not exist. If it reads "2009". The PowerShell console resembles the interactive part of PowerShell where you can enter commands and immediately get back results. The current version is 2. you are using the correct version.10: bytes=32 time<1ms TTL=128 Ping statistics for 10. Maximum = 0ms. Received = 4.10. The console relies heavily on text input.10. and then you can open it by pressing (Enter): & "C:\Users\UserA\help.0. so on version 1.0 as soon as possible.0. you won't need traditional redirection at all because PowerShell can also store the result of any command to a variable: $result = Ping 10. If it reads "2006". On older operating systems such as Windows XP or Server 2003.10. if you'd like to supplement the help information in the file with help on native commands. it is an optional component. Lost = 0 (0% loss). update to 2.10: bytes=32 time<1ms TTL=128 Reply from 10.10.0. For example. you can attach this information to the existing file with the redirection symbol ">>": Cmd /c help >> help. Approximate round trip times in milli-seconds: Minimum = 0ms.txt(Tab) The file name will automatically be completed with the absolute path name. There are plenty of special keys listed in Table 1.10.1.\help.txt (Enter) If you'd like to directly process the result of a command.txt" (Enter) You can also append data to an existing file. select Properties. By hitting (Enter). You can use your mouse to select information in the console and copy it to the Clipboard by pressing (Enter) or by right-clicking when you have the QuickEdit mode turned on. (F8) (Home) (Backspace) (Ctrl)+(C) (Ctrl)+(End) (Ctrl)+(Arrow left). If PowerShell can't understand a command. which repeats the last command. The basic settings of the console—QuickEdit mode as well as colors. the screen output will be paused once the screen is full. (F5). (PgDn) (Enter) (End) (Del) (Esc) (F2) (F4) (F7) (F8) (F9) (Left arrow). This can be accessed by right-clicking the icon to the far left in the title bar of the console window. you will have to right-click inside the console and then select Mark in a context menu. and (Tab). (Arrow down). and font sizes —can be customized in the properties window of the console. . Along with the commands. (F7) displays numbers of commands in command history Move one character to the left or right respectively Repeat the last previously entered command Moves editing cursor to beginning of command line Deletes character to the left of the insertion point Cancels command execution Deletes all characters from current position to end of command line Move insertion point one word to the left or right respectively Deletes all characters of current position up to beginning of command line Automatically completes current entry. When you pipe the results to the command more. With QuickEdit mode turned off. which completes the current entry. fonts. (Right arrow) (Arrow up). In the dialog box. you complete an entry and send it to PowerShell. (Ctrl)+(Arrow right) (Ctrl)+(Home) (Tab) Deletes the current command history Display the first (PgUp) or last (PgDn) command you used in current session Send the entered lines to PowerShell for execution Moves the editing cursor to the end of the command line Deletes the character to the right of the insertion point Deletes current command line Moves in current command line to the next character corresponding to specified character Deletes all characters to the right of the insertion point up to specified character Displays last entered commands in a dialog box Displays commands from command history beginning with the character that you already entered in the command line Opens a dialog box in which you can enter the number of a command from your command history to return the command. a number of characters in the console have special meanings and you have already become acquainted with three of them: Piping: The vertical bar "|" symbol pipes the results of a command to the next. if possible Table 1. Two special commands are cls (deletes the contents of the console) and exit (ends PowerShell). are particularly useful. and continued when you press a key.(Alt)+(F7) (PgUp). an error message appears highlighted in red stating the possible reasons for the error.1: Important keys and their meaning in the PowerShell console You will find that the keys (Arrow up). PowerShell 2.0 also comes with a simple script editing tool called "ISE" (Integrated Script Environment). You find it in PowerShell‘s jump list (if you are using Windows 7). and you can also launch it directly from PowerShell by entering ise ENTER. On Windows Server 2008 R2. You can then open and view the file contents. The symbol ">>" appends information to an existing file.5. it is an optional feature that needs to be enabled first in your system control panel. Topics Covered: PowerShell as a Calculator o Calculating with Number Systems and Units Executing External Commands o Starting the "Classic" Console o Discovering Useful Console Commands o Security Restrictions o Special Places Cmdlets: PowerShell Commands o Using Parameters o Using Named Parameters o Switch Parameter o Positional Parameters o Common Parameters Aliases: Shortcuts for Commands o Resolving Aliases o Creating Your Own Aliases o Removing or Permanently Keeping an Alias o Overwriting and Deleting Aliases Functions: PowerShell-"Macros" o Calling Commands with Arguments Invoking Files and Scripts o Starting Scripts Running Batch Files Running VBScript Files Running PowerShell Scripts Summary PowerShell as a Calculator You can use the PowerShell console to execute arithmetic operations the same way you use a calculator. you will first learn how to work with PowerShell interactively. You can do that from PowerShell as well: Import-Module ServerManager Add-WindowsFeature ISE -IncludeAll PowerShell has two faces: interactivity and script automation. Then. Even parentheses will work the same as when you use your pocket calculator: .NET Framework 3. we will take a look at PowerShell scripts. Redirection: The symbol ">" redirects the results of a command to a file. Just enter a math expression and PowerShell will give you the result: 2+4 (Enter) 6 You can use all of the usual basic arithmetic operations. In this chapter.1. ISE requires . For example.3 + 2 (Enter) 4 3 2 The comma always creates an array. $() is used inside strings so you can use this line if you'd like to insert the result of some code into text: "Number of EXE files in my windows folder: ` $(@(Dir $env:windir\*. This line will always count folder content reliably: @(Dir $env:windir\*.3333333333333 Parentheses play a special role in PowerShell. i. you can list the contents of sub-directories with the dir command and then determine the number of files in a folder by enclosing the dir command in parentheses. It then adds the number 2 to that array.5 (Enter) 11. (Dir $env:windir\*. and petabyte (PT). The previous line would have not returned the number of items if the folder contained only one or none file. PowerShell created an array with the elements 4 and 3. megabyte (MB).68888888888889 The example above calculates how many CD-ROMs can be stored on a DVD. . PowerShell will support the common unit‘s kilobyte (KB). Calculating with Number Systems and Units The next arithmetic problem is a little unusual. operations performed within parentheses have priority and ensure that multiplication operations do not take precedence over addition operations. parentheses are also important when using PowerShell commands.Count)"(Enter) Note that PowerShell always uses the decimal point for numbers. For example. As you'll discover in upcoming chapters. there are two very similar constructions: @() and $(). So the important thing to take with you is that the decimal point is always a point and not a comma in PowerShell. terabyte (TB). Some cultures use other characters in numbers. @() will also execute the code inside the brackets but return the result always as an array.(12+5) * 3 / 4. resulting in an array of three numbers. 4GB / 720MB (Enter) 5.Count (Enter) 12 Try and replace the folder with some empty folder and you still get back the correct number of items. It always uses the decimal point. So in this example. They always work from the inside out: the results inside the parentheses are produced before evaluating the expressions outside of the parentheses.Count (Enter) 12 In addition.exe -ErrorAction SilentlyContinue).exe -ErrorAction SilentlyContinue). such as a comma. Just make sure you do not use a space between a number and a unit. The array content is then dumped by PowerShell into the console. gigabyte (GB). Using a comma instead of a decimal point will return something entirely different: 4.exe). PowerShell does not care.e. (2*2)*2 = 4*2. 1333333333333 5454. You can use a format operator like -f if you'd like to see the results presented in a different way.4. a total of five operators are available.5 2273312768 261 "Hello there" 0. The units must directly follow the number and must not be separated from it by a space.5 12gb .5 12582912 2304 "xxxxx" 1. the number will be multiplied by the unit.11111111111111 34.5gb 200 . Results and formats: Numeric results are always returned as decimal values.1). Decimal point: Fractions use a point as a decimal separator (never a comma).0xAB 5 * 4.4. Brackets: Brackets group statements and ensure that expressions in parentheses are evaluated first. which ensures that they are automatically converted into decimal values. Otherwise.5 8053063680 29 22. This will be discussed in detail later in this book. which are also called "arithmetic operators" (Table2. GB. Operators evaluate the two values to the left and the right. or PB units to a number. If you add one of the KB. However. whitespace characters do matter because they are always token delimiters. For basic operations. MB. Take a look at the following command line: 12 + 0xAF (Enter) 187 PowerShell can easily understand hexadecimal values: simply prefix the number with "0x": 0xAFFE (Enter) 45054 Here is a quick summary: Operators: Arithmetic problems can be solved with the help of operators. Comma: Commas create arrays and are irrelevant for normal arithmetic operations. example 5 + 4. Whitespace characters aren't allowed between numbers and values.1mb (Enter) 1048576 These units can be in upper or lower case – PowerShell does not care. TB.25 Operator Description + Adds two values - Subtracts two values * Multiplies two values / Divides two values .5 4mb * 3 12 * 0xC0 "x" * 5 5 / 4.5 2gb + 120mb 0x100 + 5 "Hello " + "there" 5 . Special conversions: Hexadecimal numbers are designated by the prefix "0x". PowerShell will interpret the unit as a new command and will get confused because there is no such command.5 1mb / 30kb 0xFFAB / 0xC result 9. you can make PowerShell wait for Windows-based programs or control window size.168. .net [88.1. . For example. . Most text-based commands use exit to quit.208. .slr.218] over a maximum of 30 hops: 1 12 ms 7 ms 11 ms TobiasWeltner-PC [192. Press (Ctrl)+(C) to cancel a text-based command. Note that you can use the cmdlet Start-Process with all of its parameters when you want to launch an external program with special options. Type cls (Enter) to clear the console screen.net [74.208.1 This following command enables you to verify if a Web site is online and tells you the route the data packets are sent between a Web server and your computer: Tracert powershell..arcor-ip. .134] 18 145 ms 145 ms 149 ms ratdog. .5 Table 2. .5 0. .) 17 150 ms 151 ms 152 ms vl-987. .1.info [74. . . you can type exit (Enter). Windowsbased programs are executed asynchronously. . .1.1] 3 15 ms 16 ms 16 ms han-145-254-11-105. if you want to examine the settings of your network card.% Supplies the rest of division 5%4. Text-based commands are executed synchronously.54. Occasionally.com [74.1] 2 15 ms 16 ms 16 ms dslb-088-070-064-001.arcor-ip.lxa. you can launch external programs using different credentials. : 192.1.255.255.oneandone. : fe80::6093:8889:257e:8d1%8 IPv4 address .168.70.pools. However. .254. . there's a difference between text-based commands like ipconfig and Windows programs like Notepad. simply enter cmd (Enter).. Starting the "Classic" Console To temporarily switch back to the "classic" console. With Start-Process. you can enter the command ipconfig—it works in PowerShell the same way it does in the traditional console: Ipconfig Windows IP Configuration Wireless LAN adapter Wireless Network Connection: Connection-specific DNS Suffix: Connection location IPv6 Address .208. Just type notepad (Enter) or explorer (Enter). . and the console waits for the commands to complete. : 255.105] (. . ).54. . . you can easily launch it from within PowerShell.net [145.com Trace route to powershell. Even PowerShell can be closed by entering exit.11. To leave the old console.gw-ps2. .1: Arithmetic operators Executing External Commands PowerShell can also launch external programs in very much the same way as the classic console. .35 Subnet Mask . Since the old console is just another text-based command. . . . . . the command quit is required in commands instead of exit.0 Standard Gateway . : 192.218] You can execute any Windows programs.168.64. . or deletes the volume label of a disk. DEL Deletes one or more files. BREAK Sets or clears extended CTRL+C checking. MODE Configures a system device. ATTRIB Displays or changes file attributes. FC Compares two files or sets of files. CALL Calls one batch program from another. DISKCOMP Compares the contents of two floppy disks. HELP Provides Help information for Windows commands. MORE Displays output one screen at a time. MD Creates a directory. CACLS Displays or modifies access control lists (ACLs) of files. IF Performs conditional processing in batch programs. CHDIR Displays the name of or changes the current directory. FOR Runs a specified command for each file in a set of files. It's only available inside the classic console.Discovering Useful Console Commands The cmd command can be used for just one command when you specify the parameter /c. This is useful for invoking an old console command like help. CMD Starts a new instance of the Windows command interpreter. recalls Windows commands. FINDSTR Searches for strings in files. type HELP command-name ASSOC Displays or modifies file extension associations. FIND Searches for a text string in a file or files. CD Displays the name of or changes the current directory. FORMAT Formats a disk for use with Windows. CLS Clears the screen. COLOR Sets the default console foreground and background colors. EXIT Quits the CMD. DOSKEY Edits command lines. You cannot convert the current drive. Using this command will return a list of many other useful external console commands: Cmd /c Help For more information on a specific command. and creates macros. GOTO Directs the Windows command interpreter to a labeled line in a batch program. . COPY Copies one or more files to another location. FTYPE Displays or modifies file types used in file extension associations. MOVE Moves one or more files from one directory to another directory. and displays the differences between them. DATE Displays or sets the date. ECHO Displays messages. COMPACT Displays or alters the compression of files on NTFS partitions. ENDLOCAL Ends localization of environment changes in a batch file.EXE program (command interpreter). CHKNTFS Displays or modifies the checking of disk at boot time. changes. AT Schedules commands and programs to run on a computer. GRAFTABL Enables Windows to display an extended character set in graphics mode. COMP Compares the contents of two files or sets of files. or turns command echoing on or off. PATH Displays or sets a search path for executable files. DIR Displays a list of files and subdirectories in a directory. LABEL Creates. CONVERT Converts FAT volumes to NTFS. CHCP Displays or sets the active code page number. DISKCOPY Copies the contents of one floppy disk to another. This command has no external program that you can access directly from PowerShell. CHKDSK Checks a disk and displays a status report. MKDIR Creates a directory. ERASE Deletes one or more files. select Run as Administrator. SHIFT Shifts the position of replaceable parameters in batch files. PUSHD Saves the current directory then changes it. TITLE Sets the window title for a CMD. Remember to start your PowerShell explicitly with administrator rights if you must use admin privileges and have enabled User Account Control. To do this. VERIFY Tells Windows whether to verify that your files are written correctly to a disk. REPLACE Replaces files. pick some commands from the list. POPD Restores the previous value of the current directory saved by PUSHD. SUBST Associates a path with a drive letter. or removes Windows environment variables. REM Records comments (remarks) in batch files or CONFIG. START Starts a separate window to run a specified program or command.EXE session.. PROMPT Changes the Windows command prompt. you can run PowerShell without administrator privileges when experimenting with new commands. SET Displays. Figure 2. TIME Displays or sets the system time. Use an administrator command line and then run the program again. TYPE Displays the contents of a text file. VER Displays the Windows version. RECOVER Recovers readable information from a bad or defective disk.PAUSE Suspends processing of a batch file and displays a message. RMDIR Removes a directory.exe and in the context menu. (Run without administrator privileges whenever possible) Security Restrictions . TREE Graphically displays the directory structure of a drive or path. For example: Cmd /c help vol As an added safety net. You can use all of the above commands in your PowerShell console. PRINT Prints a text file.1: Run PowerShell as administrator. XCOPY Copies files and directory trees. SETLOCAL Begins localization of environment changes in a batch file. sets. right-click PowerShell.SYS. That will protect you against mistakes as most dangerous commands can no longer be executed without administrator rights: defrag c: You must have Administrator privileges to defragment a volume. VOL Displays a disk volume label and serial number. REN Renames a file or files. RD Removes a directory. SORT Sorts input. To try this. RENAME Renames a file or files. operable program or script file. quote it.exe PowerShell now treats quoted information as string and immediately outputs it back to you. you need to specify the relative path where ". you cannot launch wordpad: wordpad The term "wordpad" is not recognized as a cmdlet.exe" C:\programs\Windows NT\accessories\wordpad. function.\wordpad.exe" Finally. and then launch the program right there? Cd "C:\programs\Windows NT\accessories" wordpad. you can specify the complete path name like this: C:\programs\Windows NT\accessories\wordpad. Verify the term and try again.exe <<<< This results in another red exception because PowerShell wants a relative or absolute path. so if the program is not located in one of the standard system folders." represents the current folder: . Verify the term and try again.exe The term "wordpad" is not recognized as a cmdlet. At line:1 char:11 + wordpad. Wouldn't it be easier to switch from the current folder to the folder with the program we're looking. But that can cause another problem: "C:\programs\Windows NT\accessories\wordpad. At line:1 char:7 + wordpad <<<< Here. function. At line:1 char:21 + C:\programs\Windows <<<< NT\accessories\wordpad. operable program or script file. You can prefix it with an ampersand to ensure that PowerShell executes the quoted text: & "C:\programs\Windows NT\accessories\wordpad. operable program or script file.exe The term " C:\program" is not recognized as a cmdlet. So if path names include spaces.exe Special Places .While you can launch notepad. if you don't want to use absolute paths like in the example above. PowerShell simply did not know where to find WordPad. WordPad starts. Verify the term and try again. function.exe Since the path name includes whitespace characters and because PowerShell interprets them as separators. So. PowerShell is actually trying to start the program C:\program. Watch out for whitespace characters: If whitespace characters occur in path names.C:\program File s\ATI Technologies\ATI. That's why common programs. Specifying a path: You must tell the console where it is if the program is located somewhere else.C:\programs\Windows NT\accessories" wordpad. the tab-completion quotes the path and inserts an "&" before it. The "&" symbol will allow you to execute any string just as if you had entered the text directly on the command line. notepad.C:\Windows\System32\WindowsPowerShell\v1.ACE\Core-Static.C:\Windows\System32\Wbem. you can launch WordPad just by entering its program name. Note that your change to the environment variable Path is valid only in the current PowerShell session.0\. such as regedit. remember (Tab). such as: $env:path += ".C:\program Files\Softex\OmniPass. Cmdlets: PowerShell Commands . To do so. you can enclose the entire path in quotes so that PowerShell doesn't interpret whitespace characters as separators. If you'd like to permanently extend Path. Add a "\" and press (Tab) once again to specify the next sub-directory.exe After this change. you can add other folders containing important programs to your Path environment variables. As a clever alternative. and unless that is what you want you can avoid it by using single quotes by default.c :\program Files\Microsoft SQL Server\90\Tools\binn\. you will need to update the path environment variable in one of your profile scripts. Prefix a string with "&" to actually execute it. specify the absolute or relative path name of the program.or ipconfig work as-is and do not require you to type in the complete path name or a relative path.C:\program Files\MakeMsi\.You won't need to provide the path name or append the file extension to the command name if the program is located in a folder that is listed in the PATH environment variable.C:\Windows. Read more about profile scripts in Chapter 10.C: \program Files\QuickTime\QTSystem\ You'll find more on variables. Profile scripts start automatically when PowerShell starts and customize your PowerShell environment. as well as special environment variables. in the next chapter. You can find this list by entering: $env:Path C:\Windows\system32. The moment a whitespace character turns up in a path. You can put all your important programs in one of the folders listed in the environment variable Path. powershell. Stick to single quotes because PowerShell "resolves" text in double quotation marks. the key for automatic completion: C:\(Tab) Press (Tab) again and again until the suggested sub-directory is the one you are looking for. The "&" changes string into commands: PowerShell doesn't treat text in quotes as a command. & ("note" + "pad") If you have to enter a very long path names. replacing variables with their values. . Cmdlet Get-History Get-History [[-Id] <Int64[]>. Cmdlet Get-ChildItem Get-ChildItem [[-Path] <Stri..---. Cmdlet Get-EventSubscriber Get-EventSubscriber [[-Sourc...... .... Cmdlet Get-PSBreakpoint Get-PSBreakpoint [[-Script] . whose names always consist of an action (verb) and something that is acted on (noun). Cmdlet Get-ItemProperty Get-ItemProperty [-Path] <St." That already gives you the first part of the command name... Cmdlet Get-Credential Get-Credential [-Credential]...... Cmdlet Get-Process Get-Process [[-Name] <String... Cmdlet Get-Command Get-Command [[-ArgumentList].PowerShell‘s internal commands are called cmdlets.... This naming convention will help you to find the right command... Cmdlet Get-PSSnapin Get-PSSnapin [[-Name] <Strin. Cmdlet Get-ExecutionPolicy Get-ExecutionPolicy [[-Scope.. Cmdlet Get-AuthenticodeSignature Get-AuthenticodeSignature [-.. Cmdlet Get-Date Get-Date [[-Date] <DateTime>.. Cmdlet Get-PSCallStack Get-PSCallStack [-Verbose] [... Cmdlet Get-Transaction Get-Transaction [-Verbose] [. Cmdlet Get-Module Get-Module [[-Name] <String[. Cmdlet Get-Service Get-Service [[-Name] <String...---------Cmdlet Get-Acl Get-Acl [[-Path] <String[]>]. Cmdlet Get-Host Get-Host [-Verbose] [-Debug].... Cmdlet Get-PSSession Get-PSSession [[-ComputerNam. Let's take a look at how the system works. Cmdlet Get-Culture Get-Culture [-Verbose] [-Deb.2).. Cmdlet Get-EventLog Get-EventLog [-LogName] <Str..... Cmdlet Get-Member Get-Member [[-Name] <String[. Cmdlet Get-Location Get-Location [-PSProvider <S... Cmdlet Get-Counter Get-Counter [[-Counter] <Str.. Cmdlet Get-PfxCertificate Get-PfxCertificate [-FilePat...... Cmdlet Get-ComputerRestorePoint Get-ComputerRestorePoint [[-. Cmdlet Get-TraceSource Get-TraceSource [[-Name] <St... Cmdlet Get-FormatData Get-FormatData [[-TypeName] .. Cmdlet Get-Random Get-Random [[-Maximum] <Obje.. Cmdlet Get-PSSessionConfiguration Get-PSSessionConfiguration [. Cmdlet Get-Help Get-Help [[-Name] <String>] .. Cmdlet Get-HotFix Get-HotFix [[-Id] <String[]>.. the proper verb is "get... There are relatively few verbs that the strict PowerShell naming conditions permit (Table 2... If you're looking for a command for a certain task.. Cmdlet Get-Event Get-Event [[-SourceIdentifie.. Cmdlet Get-PSDrive Get-PSDrive [[-Name] <String...... If you know that you want to obtain something. The mother of all cmdlets is called Get-Command: Get-Command -commandType cmdlet It retrieves a list of all available cmdlets.. Cmdlet Get-Alias Get-Alias [[-Name] <String[]. and now all you have to do is to take a look at a list of commands that are likely candidates: Get-Command -verb get CommandType Name Definition ----------.... you can first select the verb that best describes the task.. Cmdlet Get-Job Get-Job [[-Id] <Int32[]>] [-...... Cmdlet Get-PSProvider Get-PSProvider [[-PSProvider... Cmdlet Get-Content Get-Content [-Path] <String[... Cmdlet Get-Item Get-Item [-Path] <String[]> . you can enter the sub-directory name after the cmdlet: Get-ChildItem c:\windows You can use Get-Help to output full help on Get-ChildItem to find out which parameters are supported: Get-Help Get-ChildItem -Full This will give you comprehensive information as well as several examples.... You can list it with Get-Verb... You can also look up help for any cmdlet using Get-Help: Get-Help Get-Command -detailed You can easily discover cmdlets because Get-Command allows wildcards: Get-Command *service* -CommandType cmdlet CommandType Name Definition ----------.. Using Parameters Parameters add information so a cmdlet knows what to do.. For example. Cmdlet New-Service New-Service [-Name] <String>.. Cmdlet Get-WSManInstance Get-WSManInstance [-Resource... the cmdlet Get-ChildItem lists the contents of the current subdirectory...... Cmdlet Get-Unique Get-Unique [-InputObject <PS. Cmdlet Get-Variable Get-Variable [[-Name] <Strin. There is an approved list of verbs that are used with cmdlet names...---------Cmdlet Get-Service Get-Service [[-Name] <String. Cmdlet Get-WSManCredSSP Get-WSManCredSSP [-Verbose] . Cmdlet Get-WinEvent Get-WinEvent [[-LogName] <St.. The contents of the current folder will be listed if you enter the cmdlet without additional parameters: Get-ChildItem For example... Of particular interest is the "Parameters" section that you can also retrieve specifically for one or all parameters: Get-Help Get-ChildItem -Parameter * .. Cmdlet Start-Service Start-Service [-Name] <Strin. if you'd prefer to get a list of the contents of another sub-directory.....Cmdlet Get-UICulture Get-UICulture [-Verbose] [-D. Cmdlet Set-Service Set-Service [-Name] <String>.. Get-Help will show you which parameters are supported by any given cmdlet. Cmdlet Stop-Service Stop-Service [-Name] <String. Cmdlet Get-WmiObject Get-WmiObject [-Class] <Stri. Cmdlet New-WebServiceProxy New-WebServiceProxy [-Uri] <. Cmdlet Suspend-Service Suspend-Service [-Name] <Str.. Cmdlet Restart-Service Restart-Service [-Name] <Str... Cmdlet Resume-Service Resume-Service [-Name] <Stri..... Function Get-Verb .---. Once again.. Implementation varies from provider to provider. The value of this parameter qualifies the Path parameter. such as "*. because the provider applies them when retrieving the objects. Wildcards are permitted. depends on the provider. Enter a path element or pattern. Even using the Force parameter. Required? Position? Default value Accept pipeline input? Accept wildcard characters? -Include <string[]> Retrieves only the specified items.txt". including the use of wildcards. such as hidden or system files. Required? Position? Default value Accept pipeline input? Accept wildcard characters? -Force <string[]> Allows the cmdlet to get items that cannot otherwise not be accessed by the user. Required? Position? Default value Accept pipeline input? Accept wildcard characters? -Filter <string[]> Specifies a filter in the provider's format or language. see about_Providers. such as "*. The Include parameter is effective only when the command includes the Recurse parameter or the path leads to the contents of a directory. For more information. such as C:\Windows\*.-Exclude <string[]> Omits the specified items. Enter a path element or pattern. where the wildcard character specifies the contents of the C:\Windows directory. The syntax of the filter. the cmdlet cannot override security restrictions. The value of this parameter qualifies the Path parameter. The value of this parameter qualifies the Path parameter.txt". Filters are more efficient than other parameters. rather than having Windows PowerShell filter the objects after they are retrieved. Wildcards are permitted. Required? Position? Default value false named false named false false false 2 false false false named false false . ) false 1 false false true 1 true (ByPropertyName) false Using Named Parameters Named parameters really work like key-value pairs.. You can specify the name of a parameter (which always starts with a hyphen). Required? Position? Default value Accept pipeline input? Accept wildcard characters? (. Unlike Path. .exe that are located somewhere in the folder c:\windows or in one of its sub-directories. only the item names are sent.exe -r -n Get-ChildItem : Parameter cannot be processed because the parameter name 'f' is ambiguous. No characters are interpreted as wildcards. PowerShell will report ambiguities and list the parameters that are conflicting: Get-ChildItem -pa c:\windows -f *. then the value you want to assign to the parameter. Let's say you'd like to list all files with the extension *. Required? Position? Default value Accept pipeline input? Accept wildcard characters? -Name <string[]> Retrieves only the names of the items in the locations. Possible matches include: -Filter -Force.exe -recurse -name There are clever tricks to make life easier..exe -r -n Just play with it: If you shorten parameter names too much. Single quotation marks tell Windows PowerShell not to interpret any characters as escape sequences. then a space.Accept pipeline input? false Accept wildcard characters? false -LiteralPath <string[]> Specifies a path to one or more locations. You can use this command: Get-ChildItem -path c:\windows -filter *. You don't have to specify the complete parameter name as long as you type as much of the parameter name to make it unambiguous: Get-ChildItem -pa c:\windows -fi *. the value of LiteralPath is used exactly as it is typed. enclose it in single quotation marks. If you pipe the output of this command to another command. If the path includes escape characters. That's why you could have expressed the command we just discussed in one of the following ways: Get-ChildItem c:\windows *. parameters really are no key-value pairs but simple yes/no-switches. If they're specified. the parameter -recurse ensures that Get-ChildItem searches not only the -path specified sub-directories. If they're left out." This enables you to omit the parameter name altogether and simply specify the arguments.consoleColor" and try again. PowerShell will identify and eliminate the named arguments -recurse and -name first because they are clearly specified. your arguments need to be submitted in just the right order according to their position numbers. The remaining arguments are "unnamed" and need to be assigned based on their position: . At line:1 char:27 + Write-Host -BackgroundColor <<<< You can always quote the text.) Positional Parameters For some often-used parameters.exe Get-ChildItem -name c:\windows *.. This is necessary only rarely when the argument reads like a parameter name Write-Host -BackgroundColor Write-Host : Missing an argument for parameter 'BackgroundColor'.exe -r -n You can also turn off parameter recognition. they turn on a certain functionality..exe -recurse In all three cases. but all sub-directories. Everything following these two symbols will no longer be recognized as a parameter: Write-Host "-BackgroundColor" -BackgroundColor Write-Host -. PowerShell assigns a "position. The help on Get-ChildItem will clearly identify switch parameters and place a "<SwitchParameter>" after the parameter name: Get-Help Get-Childitem -parameter recurse -recurse <SwitchParameter> Gets the items in the specified locations and all child items of the locations. Or you can expressly turn off parameter recognition by typing "--". For example. they don't turn on the function. (.At line:1 char:14 + Get-ChildItem <<<< -pa c:\windows -f *.exe -recurse -name Get-ChildItem -recurse -name c:\windows *. Specify a parameter of type "System.-BackgroundColor -BackgroundColor Switch Parameter Sometimes. With positional parameters. And the switch parameter -name makes Get-ChildItem output only the names of files (as string rather than rich file and folder objects). Wildcards are permitted. Common Parameter -Verbose Type Description Switch Generates as much information as possible. positional parameters.Get-ChildItem c:\windows *. and abbreviated parameter names. you should not use these shortcuts. you will want short and fast commands so use aliases. continues SilentStop: Displays no error message.exe The parameter -path has the position 1. Without this switch. stops Inquire: Asks how to proceed You can find more information in Chapter 11. The position specification will make it easier to use a cmdlet because you don't have to specify any parameter names for the most frequently and commonly used parameters. PowerShell attaches the first remaining argument to this parameter. The default location is the current directory (. -ErrorVariable Value Name of a variable in which in the event of an error information about the error is stored. -path <string[]> Specifies a path to one or more locations. you can use the true cmdlet names and stick to fully named parameters. Required? false Position? 1 Standard value used <NOTE: if not specified uses the Current location> Accept pipeline input? true (ByValue. Once you write PowerShell scripts. One reason is that scripts can be portable and not depend on specific aliases you may have defined. Consequently. You can find more information in Chapter 11. it is assigned the second remaining argument. Determines how the cmdlet responds when an error occurs. type "get-help about_commonparameters". -Debug. -ErrorVariable. the cmdlet restricts itself to displaying only essential information Outputs additional warnings and error messages that help programmers find the causes of -Debug Switch errors. scripts are more complex and need to be as readable and understandable as possible. Common Parameters Cmdlets also support a set of generic "CommonParameters": <CommonParameters> This cmdlet supports the common parameters: -Verbose. For more information.). Permitted values: NotifyContinue: Reports error and continues (default) NotifyStop: Reports error and stops -ErrorAction Value SilentContinue: Displays no error message. Second. . Here is a tip: In daily interactive PowerShell scripting. and -OutVariable. Instead. These parameters are called "common" because they are permitted for (nearly) all cmdlets and behave the same way. -ErrorAction. ByPropertyName) Accept wildcard characters? true The parameter -filter has the position 2. Named parameters help other people better understand what you are doing. and no value has yet been assigned to it. So. This parameter is usually superfluous because you can directly assign the value to a variable.---. There are pre-defined "historic" aliases called "dir" and "ls" which both point to the PowerShell cmdlet Get-ChildItem. both Windows admins and UNIX admins.-OutVariable You can find more information in Chapter 11. The difference is that it will no longer be displayed in the console if you assign the result to a variable. This is why PowerShell has a built-in alias system as it comes with a lot of pre-defined aliases. you will want short and familiar commands. aliases have two important tasks in PowerShell: Historical: NFind and use important cmdlets by using familiar command names you know from older shells Speed: Fast access to cmdlets using short alias names instead of longer formal cmdlet names Resolving Aliases Use these lines if you'd like to know what "genuine" command is hidden in an alias: $alias:Dir Get-ChildItem $alias:ls Get-ChildItem Get-Command Dir Get-Command Dir CommandType Name Definition ----------. This is why in PowerShell. In every day admin life.---------Alias dir Get-ChildItem Alias gci Get-ChildItem Alias ls Get-ChildItem Get-ChildItem c:\Dir c:\ls c:\ So.3: Common parameters in effect for (nearly) all cmdlets Aliases: Shortcuts for Commands Cmdlet names with their verb-noun convention are very systematic. Get-Command dir CommandType Name Definition ----------.---------Alias dir Get-ChildItem .---.---------Alias dir Get-ChildItem Get-Alias -Definition Get-Childitem CommandType Name Definition ----------. yet not always practical. can list folder contents with commands they are accustom to using.---. Name of a variable in which the result of a cmdlet is to be stored. Value $result = Get-ChildItem It will be output to the console and stored in a variable if you assign the result additionally to a variable: Get-ChildItem -OutVariable result Table 2. %} 1 Format-Table {ft} 1 Format-Wide {fw} 1 Get-Alias {gal} 3 Get-Content {gc. Get-alias -definition Get-ChildItem This will get you all aliases pointing to the cmdlet or command you submitted to -Definition. ls. You will receive a list of individual alias definitions: Get-alias -name Dir Get-ChildItem You can use the parameter -Definition to list all aliases for a given cmdlet. You can also get the list of aliases using the cmdlet Get-Alias. PowerShell supports many additional virtual drives. cp.$alias:Dir lists the element Dir of the drive alias:. The result would be a list of aliases in their entirety: Dir alias: CommandType Name Definition ----------. That may seem somewhat surprising because there is no drive called alias: in the classic console.---. As it turns out.. If you want to know more.---------alias ac Add-Content alias asnp Add-PSSnapin alias clc Clear-Content (. There are more approaches to the same result. there's even a third alias for Get-ChildItem called "gci".----1 Add-Content {ac} 1 Add-PSSnapin {asnp} 1 Clear-Content {clc} 1 Clear-Item {cli} 1 Clear-ItemProperty {clp} 1 Clear-Variable {clv} 3 Copy-Item {cpi.---. cat. type} 3 Get-ChildItem {gci. you can submit it to Get-Command. Dir} 1 Get-Command {gcm} .) Get-Command can also resolve aliases. The next examples find alias definitions by doing a keyword search and by grouping: Dir alias: | Out-String -Stream | Select-String "Get-ChildItem" Count Name Group ----. and it will tell you the command type and where it is located. copy} 1 Copy-ItemProperty {cpp} 1 Convert-Path {cvpa} 1 Compare-Object {diff} 1 Export-Alias {epal} 1 Export-Csv {epcsv} 1 Format-Custom {fc} 1 Format-List {fl} 2 ForEach-Object {foreach. You can also list alias: like any other drive with Dir. Whenever you want to know more about a particular command. and alias: is only one of them.. the cmdlet Get-PSDrive lists them all. pwd} 1 Get-Member {gm} 1 Get-ItemProperty {gp} 2 Get-Process {gps. cd.} 2 Rename-Item {rni. ?} 2 Write-Output {write. r} 1 Invoke-Item {ii} 1 Import-Alias {ipal} 1 Import-Csv {ipcsv} 3 Move-Item {mi.. mv.. ren} 1 Rename-ItemProperty {rnp} 1 Remove-ItemProperty {rp} 1 Remove-PSSnapin {rsnp} 1 Remove-Variable {rv} 1 Resolve-Path {rvpa} 1 Set-Alias {sal} 1 Start-Service {sasv} 1 Set-Content {sc} 1 Select-Object {select} 1 Set-Item {si} 3 Set-Location {sl. rm. kill} 1 Stop-Service {spsv} 2 Set-Variable {sv. chdir} 1 Start-Sleep {sleep} 1 Sort-Object {sort} 1 Set-ItemProperty {sp} 2 Stop-Process {spps.1 Get-PSDrive {gdr} 3 Get-History {ghy. history} 1 Get-Item {gi} 2 Get-Location {gl. cls} 1 Out-Printer {lp} 1 Pop-Location {popd} 1 Push-Location {pushd} Creating Your Own Aliases . rmdir. mount} 1 New-Item {ni} 1 New-Variable {nv} 1 Out-Host {oh} 1 Remove-PSDrive {rdr} 6 Remove-Item {ri. ps} 1 Group-Object {group} 1 Get-Service {gsv} 1 Get-PSSnapin {gsnp} 1 Get-Unique {gu} 1 Get-Variable {gv} 1 Get-WmiObject {gwmi} 1 Invoke-Expression {iex} 2 Invoke-History {ihy. set} 1 Tee-Object {tee} 2 Where-Object {where. move} 1 Move-ItemProperty {mp} 1 New-Alias {nal} 2 New-PSDrive {ndr. h. del. echo} 2 Clear-Host {clear. That is. Take a look at the next example: Edit Set-Alias edit notepad.exe Edit Edit typically launches the console-based Editor program. Import and export: You can use the built-in import and export function for aliases. At line:1 char:13 + Import-Alias <<<< alias1 Import-Alias will notify you that it cannot create some aliases of the list because these aliases already exist. Automated in a profile: Let your alias be set automatically when PowerShell starts: add your aliases to a start profile. You can press (Alt)+(F) and then (X) to exit without completely closing the console window. Type in: alias1 (Enter) The list will be saved. You can look at the list afterwards and manipulate it. but the Notepad. enter: Export-Alias Because you haven't entered any file names after Export-Alias. rather theoretical. you might want the list to include a few of your own alias definitions: Notepad alias1 You can import the list to activate the alias definitions: Import-Alias alias1 Import-Alias : Alias not allowed because an alias with the name "ac" already exists. All new aliases are discarded as soon as you exit PowerShell. Try these options if you'd like to keep your own aliases permanently: Manually each time: Set your aliases after every start manually using Set-Alias.exe".Set-Alias adds additional alias definitions. All of your own aliases will be gone the next time you start PowerShell. if you'd like to export all currently defined aliases as a list to a file. You can actually override commands with aliases since aliases have precedence over other commands. The next time you enter it. If you create a new alias called "Edit" and set it to "notepad. the command will ask you what the name are under which you want to save the list. $alias:edit Removing or Permanently Keeping an Alias How do you remove aliases? You don't. Specify additionally the option -Force to ensure that Import-Alias overwrites existing aliases: . For example. "Built-in" aliases like "dir" and "cd" will still be there. the command Edit will be re-programmed. For example. You'll learn how to do this in Chapter 10. of course. PowerShell will no longer run the old Editor program. 10.10. Calling Commands with Arguments If you find yourself using the command ping frequently to verify network addresses. Perhaps you're already familiar with the command from the classic console: Del C:\garbage.10: bytes=32 time<1ms TTL=128 Ping statistics for 10.Import-Alias alias1 -Force You can add the Import-Alias instruction to your start profile and specify a permanent path to the alias list.10. the arguments of a command can never be included in an alias.10. the uniform provider approach becomes evident. you can add new aliases.Definition -ea SilentlyContinue)) {$_}} Functions: PowerShell-"Macros" Aliases are simple shortcuts to call commands with another name (shortcut names).10. it will suffice to update the alias list with Export-Alias and to write over the old file.10.10 with 32 bytes of data: Reply from 10. Then. However. or to make the transition to PowerShell easier (historic aliases).10 Aliases won't work here because they can't specify command arguments.10 Pinging 10. Just redefine the alias with the cmdlet Set-Alias. Maximum = 0ms.10. Received = 1.10. you may want to make this easier by creating a shortcut that not only calls ping.10. Approximate round trip times in milli-seconds: Minimum = 0ms. Use this command if you'd like to remove an alias completely and don't want to wait until you end PowerShell: Del alias:edit This instruction deletes the "Edit" alias.10: Packets: Sent = 1. This is one way for you to keep your aliases permanently. Overwriting and Deleting Aliases You can overwrite aliases with new definitions any time as long as an alias is not write-protected. Functions can: function quickping { ping -n 1 -w 100 $args } quickping 10. Let's see how you can automate this call: Ping -n 1 -w 100 10. This will make PowerShell automatically read this alias list when it starts. Average = 0ms . Here.txt Here is an example that finds all aliases that point to no valid target. Later.exe. but also appends standard arguments to it. Lost = 0 (0% loss). You will need to use functions if you want that.10. The very same "Del" command will allow you to delete files and sub-directories in the file system as well. which is a great way of finding outdated or damaged aliases: Get-Alias | ForEach-Object { if (!(Get-Command $_. htm # File cannot be opened directly: test.10.bat Now enter this text: @echo off echo An attacker can do dangerous things here pause Dir %windir% . You'll read more about functions later.10.bat". # Save information on all running processes to HTML file # (lasts several seconds): Get-Process | ConvertTo-Html | Out-File test. Average = 0ms Unlike alias definitions. At line:1 char:8 + test. They may include all the commands allowed in a classic cmd.10 with 32 bytes of data: Reply from 10.Set-Alias qp quickping qp 10. Received = 1.10 Pinging 10. you specify an absolute or relative path. Lost = 0 (0% loss).10: Packets: Sent = 1.10. First. Invoking Files and Scripts To run files (like documents or scripts). When a batch file is opened. operable program. Maximum = 0ms. Let's check it out.10.10.10: bytes=32 time<1ms TTL=128 Ping statistics for 10.htm Starting Scripts Scripts and batch files are pseudo-executables. functions can run arbitrary code that is placed in brackets.10.htm" is not recognized as a cmdlet. Approximate round trip times in milli-seconds: Minimum = 0ms. Running Batch Files Batch files are text files with the extension ". function.htm <<<< # Specify a relative or absolute path name: .10. Verify the term and try again.exe console. $args is an array and holds every piece of extra information submitted by the caller as separate array elements.10.htm The term "test. but it can be run by its associated script interpreter. Any additional information a user submitted to the function can be found in $args if you don't specify explicit parameters.\test. or script file. or the file needs to be located in one of the special trustworthy folders defined in the Path environment variable. PowerShell uses the same rules that apply to executables: either. create this test file: Notepad ping. the classic console immediately starts to execute the commands it contains. The script itself is just a plain text file. 10. find the batch file. Because it has the same name and you didn't specify any IP address or Web site address. the ping command spits out its internal help message.pause Dir %windir%\system Save the text and close Notepad.10. This way. If you were using the classic console. harmless though it might seem.bat" into your current folder. You can even store the results into a variable and process it inside PowerShell: $name = cscript. you're going to have to specify either the relative or absolute path name. Such a mix-up will never happen in the PowerShell console. A classic console doesn't distinguish between files and commands. you can easily merge VBScript logic into your PowerShell solutions.\ping Your batch file will open and then immediately runs the commands it contains. Switch over to the classic console to see for yourself: Cmd Ping 10. The information entered into the dialog is then output to the console where PowerShell can receive it.exe c:\samples\test.exec:\samples\test. . If an attacker had smuggled a batch file named "ping. PowerShell has just defended a common attack.vbs".vbs: result = InputBox("Enter your name") WScript. What we have just discussed about batch files also applies to these scripts: Notepad test. could have had catastrophic consequences. run the script: Cscript. If you want to launch your batch file. Your batch file is ready for action. return to your much-safer PowerShell environment: Exit Running VBScript Files VBScript is another popular automation language as its scripts are tagged with the file extension ". So. It will look first in the current folder. .10 An attacker can do dangerous things here Press any key .vbs (Enter) The script opens a small dialog window and asks for some information.Echo result Next. and execute it immediately.vbs Enter this VBScript code in Notepad and save it as test. Try to launch the batch file by entering its name: Ping The batch file won't run. then the ping command. .vbs . you would have been tricked by the attacker. also try using the -List parameter with Get-ExecutionPolicy: Get-ExecutionPolicy -List Scope ExecutionPolicy ------------------- .ps1 You can now enter any PowerShell code you want. Replace RemoteSigned with Bypass. Try calling wscript."Your name is $name" If you do not get back the name you entered into the dialog. such as the Internet. but instead the VBScript copyright information. Once saved.ps1 File "C:\Users\UserA\test.ps1 Try to run your script after you've created it: .ps1".exe to open the settings dialog. To restore the original setting. you can also open your script with more sophisticated and specialized script editors. set the setting to Undefined: Set-ExecutionPolicy Undefined -Scope CurrentUser To get a complete picture. PowerShell comes with an editor called PowerShell ISE. The safest way is to turn off the copyright message explicitly: $name = cscript. For now.ps1 <<<< You'll probably receive an error message similar to the one in the above example. At line:1 char:10 + . You need to allow PowerShell to execute scripts first. This is to protect you from malicious scripts. This only needs to be done once: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser This grants permission to run locally stored PowerShell scripts. and turn off the logo. you already know enough to write your first script. then the VBScript interpreter has output the copyright information first.\test. Please see "get-help about_signing" for more details. but if you want to. and save the file. Running PowerShell Scripts PowerShell has its own script files with the file extension ".vbs You can also generally turn off VBScript logos. you can turn this security feature off. Use the Windows editor to create and open your first script: Notepad $env:temp\test. which got in the way. will need to carry a valid digital signature or else they won't run. and here is how you'd open the file you created with Notepad: Ise $env:temp\test. The implications of signatures and other security settings will be discussed in Chapter 10. All PowerShell scripts are initially disabled.exe //NOLOGO c:\samples\test.\test. While you will learn much more about PowerShell scripts in Chapter 10. the line above is enough for you to experiment with your own PowerShell scripts.ps1" cannot be loaded because the execution of scripts is disabled on this system. Scripts from untrusted sources. this will get you a list if you wanted to find all cmdlets dealing with event logs: Get-Command -Noun EventLog Search for the verb "Stop" to find all cmdlets that stop something: Get-Command -Verb Stop You can also use wildcards. will affect any user on your machine. we recommend not using the "Bypass" setting if you are new to PowerShell. Summary The PowerShell console can run all kinds of commands interactively. For example. Cmdlets are PowerShell's own internal commands. you can either guess or use Get-Command." This can be useful if you must run scripts regularly that are stored on file servers outside your domain." If all policies are set to "Undefined. Note: To turn off signature checking altogether. The default setting for this scope is "Restricted. you can use Get-Help to get more information." which is the only scope available in PowerShell v. "LocalMachine. then nothing is wrong by turning off signature checking.1. If you are confident you can safely identify malicious scripts. Always remember: execution policy exists to help and protect you from potentially malicious scripts. you can set the execution policy to "Bypass. To find a particular command. However. That's how you can change this scope without special privileges. A cmdlet name is always composed of a verb (what it does) and a noun (where it acts upon). This function will help you view help information page by page: Get-Help Stop-Computer Help Stop-Computer -examples Help Stop-Computer -parameter * Cmdlets are just one of six command types you can use to get work done: . you may get security warnings and confirmation dialogs. "CurrentUser" will affect only you. The scope "Process" refers to your current session only. Otherwise. This will list all cmdlets with the keyword "computer": Get-Command *computer* -commandType cmdlet Once you know the name of a particular cmdlet. You simply enter a command and the console will return the results. No other PowerShell session will be affected by your change. So. but no other users. This is the perfect place for companies to set initial defaults that can be overridden.MachinePolicy Undefined UserPolicy Undefined Process Undefined CurrentUser RemoteSigned LocalMachine Restricted You now see all execution policies. The first two are defined by Group Policy so a corporation can centrally control execution policy." then scripts are prohibited. you can use this scope if you want to only temporarily change the execution policy." The effective execution policy is the first policy from top to bottom in this list that is not set to "Undefined. if you define an alias like "ping". we'll explain what variables are and how you can use them to solve more complex problems. Topics Covered: Personal Variables o Selecting Variable Names o Assigning and Returning Values o Assigning Multiple Variable Values o Exchanging the Contents of Variables o Assigning Different Values to Several Variables Listing Variables o Finding Variables o Verify Whether a Variable Exists o Deleting Variables Using Special Variable Cmdlets o Write-Protecting Variables: Creating Constants o Variables with Description "Automatic" PowerShell Variables Environment Variables o Reading Environment Variables o Searching for Environment Variables o Modifying Environment Variables o Permanent Modifications of Environment Variables Scope of Variables o Automatic Restriction o Changing Variable Visibility o Setting Scope Variable Type and "Strongly Typing" o Strongly Typing o The Advantages of Specialized Types Variable Management: Behind the Scenes o Modification of Variable Options o Write Protecting Variables o Examining Strongly Typed Variables o Validating Variable Contents Summary Personal Variables . Alias: Shortcuts to other commands. One way of doing this is by using variables. So. it will be used instead of ping. such as dir or ls Function: "Macros" that run code and resemble "self-made" new commands Cmdlet: Built-in PowerShell commands Application: External executables. VBScript script files. such as ipconfig. since the command type "Alias" is at the top of that list. ping or notepad PowerShell scripts: Files with extension *.exe and thus can override any other command type. PowerShell will stick to the order of that list.ps1 which can contain any valid PowerShell code Other files: Batch files. or any other file associated with an executable If commands are ambiguous. In this chapter. It is time to combine commands whenever a single PowerShell command can't solve your problem. PowerShell can store results of one command in a variable and then pass the variable to another command. However. This way. you have to specify the variable data type or else PowerShell will treat your input as simple string.8 # Replace variables in text with values: $text = "Net amount $amount matches gross amount $result" $text Net amount 120 matches gross amount 142. The only thing you do need to know is that variable names are always prefixed with a "$" to access the variable content. you can easily change the information. PowerShell creates new variables automatically so there is no need to specifically "declare" variables. The following example stores two pieces of information in variables and then calculates a new result: # Create variables and assign to values $amount = 120 $VAT = 0. your script can interactively ask for the variable content: [Int]$amount = "Enter amount of money" [Double]$VAT = "Enter VAT rate" Note that I strongly-typed the variables in this example.8 Of course. Simply assign data to a variable. Just make sure to use double-quotes to do that. be sure to enclose the variable name in brackets: ${#this is a strange variable name} = 12 ${#this is a strange variable name} 12 Assigning and Returning Values . variables are the prerequisite for reusable code.Variables store pieces of information. If you used those in your variable names. you can first gather all the information you may need and store them in variables. either by manually assigning different values to your variables or by assigning user-defined values to your variables. Selecting Variable Names You are free to call the variable anything you like – as long as the name is not causing misunderstandings. PowerShell can get confused. Simple text is something very different from numbers and you cannot calculate with pieces of text. But if you must use them for any reason. you can have hard-coded the numbers you multiplied. By assigning your data to variables. You will hear more about variable typing later in that character . So the best thing is to first avoid special characters in your variable names. Single-quoted text will not expand variable values. but whenever you use Read-Host or another method that accepts user input. Variable names are always case-insensitive. There are some special characters that have special meaning to PowerShell. You can then output the variable content by entering the variable name or you can merge the variable content into strings.19 # Calculate: $result = $amount * $VAT # Output result $result 22. By simply replacing the first two lines. you can use the assignment operator to assign values to multiple variables at the same time: # Populate several variables with the same value in one step: $a = $b = $c = 1 $a 1 $b 1 $c 1 Exchanging the Contents of Variables Now and then you might want to exchange the contents of two variables. You can assign almost anything to a variable. . . . . .) # Temporarily store the result of a legacy external command: $result = ipconfig $result Windows IP Configuration Ethernet adapter LAN Connection: Media state . . .PowerShell. even complete command results: # Temporarily store results of a cmdlet: $listing = Get-ChildItem c:\ $listing Directory: Microsoft. .The assignment operator "=" assigns a value to a variable. . In traditional programming languages. . . : Medium disconnected Connection-specific DNS Suffix: Wireless LAN adapter wireless network connection: Media state . . : Medium disconnected Connection-specific DNS Suffix: Ethernet adapter LAN Connection 2: Media state . . . . . . . : Medium disconnected Connection-specific DNS Suffix: Assigning Multiple Variable Values If you'd like. . . . .Core\FileSystem::C:\ Mode LastWriteTime Length Name (. . ... . . that would require several steps: . . . . . you'll see many more. The comma is used to create arrays. $Value2 = 10. which is accessible via a virtual drive called variable:. It consists of several commands piped together. it is possible because of arrays.20 $Value1. which are basically a list of values. dir variable: | Out-String -stream | Select-String " 20 " ." You'll learn more about this soon. swapping variable content is much easier because you can assign multiple values to multiple variables. Here is how you see all currently defined variables: Dir variable: Aside from your own personal variables.$Value1 = 10 $Value2 = 20 $Temp = $Value1 $Value1 = $Value2 $Value2 = $Temp With PowerShell. $Value2 = $Value2. If you assign one list of values to another list of values. $Value2 = 20 $Value1. $Value2 = $Value2. Have a look: # Populate several variables with the same value in one step: $Value1. Have a look: # Exchange variable values: $Value1 = 10. $Value1 Listing Variables PowerShell keeps a record of all variables. PowerShell also defines variables and calls them "automatic variables." try this: Dir variable:*maximum* Name Value -------MaximumErrorCount 256 MaximumVariableCount 4096 MaximumFunctionCount 4096 MaximumAliasCount 4096 MaximumDriveCount 4096 MaximumHistoryCount 1000 The solution isn't quite so simple if you'd like to know which variables currently contain the value 20. PowerShell can assign multiple values at the same time. $Value1 Assigning Different Values to Several Variables When you swap variable content like in the past example. If you'd like to see all the variables containing the word "Maximum. Finding Variables Using the variable: virtual drive can help you find variables. 1. again. You can use this technique to find out whether you are running PowerShell v1 or v2: # Verify whether the variable $psversiontable exists which is present only in PS v2: Test-Path variable:\psversiontable True # Use this information to check for PS v2 If (Test-Path variable:psversiontable) { 'You are running PowerShell v2' } else { 'You are running PowerShell v1 and should update to v2' } False Deleting Variables PowerShell will keep track of variable use and remove variables that are no longer used so there is no need for you to remove variables manually. Select-String selects the lines that include the desired value. This makes a variable into a constant. such as a description or write protection. Set-Variable does the same for existing variables. do exactly as you would in the file system: # create a test variable: $test = 1 # verify that the variable exists: Dir variable:\te* # delete variable: del variable:\test # variable is removed from the listing: Dir variable:\te* Using Special Variable Cmdlets To manage your variables. the output from Dir is passed on to Out-String. White space is added before and after the number 20 to ensure that only the desired value is found and not other values that contain the number 20 (like 200). Similar to files. filtering out the rest. New-Variable enables you to specify options. The parameter Stream ensures that every variable supplied by Dir is separately output as string. PowerShell provides you with the five separate cmdlets listed in Table 3.value2 20 $ 20 Here. . which converts the results of Dir into string. variables are stored in their own "drive" called variable: and every variable has a path name that you can verify with Test-Path. Two of the five cmdlets offer substantially new options: 1. you can verify whether a certain file exists. If you'd like to delete a variable immediately. Verify Whether a Variable Exists Using the cmdlet Test-Path. it can neither be modified nor deleted. you can create a variable with the Constant option.2. Only when you quit PowerShell are constants removed. # Create new variable with description and write-protection: New-Variable test -value 100 -description ` "test variable with write-protection" -option ReadOnly $test 100 # Variable contents cannot be modified: $test = 200 The variable "test" cannot be overwritten since it is a constant or read-only. as long as the variable is not a constant or is created by the system. PowerShell doesn't distinguish between variables and constants. Now. They work like variables with a write-protection. Creates a new variable and can set special variable options. You'll receive an error message if you try it anyway. a write-protected variable can still be modified by deleting it and creating a new copy of it. In the following example. Cmdlet ClearVariable GetVariable NewVariable RemoveVariable Get-Variable enables you to retrieve the internal PowerShell variables store. You'll have to specify the parameter -Force to delete it: del variable:\test -force $test = 200 As you just saw. the write-protected variable $test is created with a fixed value of 100. Get-Variable a New-Variable value 12 RemoveVariable a same as: del variable:\a Set-Variable a 12 same as: $a = 12 SetVariable Resets the value of variable or variable options. In addition. Description Example Clear-Variable Clears the contents of a variable. a description is attached to the variable. it does offer you the option of writeprotecting a variable. If a data or object type is specified for the variable. At line:1 char:6 + $test <<<< = 200 The variable is now write-protected and its value may no longer be changed. but not the variable itself. If you need stronger protection. $null Gets the variable object. However.1: Cmdlets for managing variables Write-Protecting Variables: Creating Constants Constants store a constant value that cannot be modified. The subsequent value of the a variable is NULL (empty). and its contents. Because the variable is write-protected. by using same as: $a = Clear-Variable the type of the objected stored in the variable will be preserved. Variables with the Constant option may only be . such as a description and creates a variable if it does not exist. it behaves like a read-only file. Table 3. Deletes the variable. not the value in which the variable is stored. $available = 123 New-Variable available -value 100 -description "test variable" -force Variables with Description Variables can have an optional description to help you keep track of what the variable was intended for. you cannot make it constant anymore because you‘ll get an error message: #New-Variable cannot write over existing variables: New-Variable test -value 100 -description ` "test variable with copy protection" -option Constant New-Variable : A variable named "test" already exists. However. If the variable is write-protected. carry out the process with the Force parameter. At line:1 char:13 + New-Variable <<<< test -value 100 -description "test variable" # normal variables may be overwritten with -force without difficulty. New-Variable can create # a new one with the "Constant" option: del variable:\test -force New-Variable test -value 100 -description ` "test variable with copy protection" ` -option Constant # variables with the "Constant" option may neither be # modified nor deleted: del variable:\test -force Remove-Item : variable "test" may not be removed since it is a constant or write-protected. If a variable already exists. Variables of the constant type are unchangeable once they have been created and -Force does not change this: # Parameter -force overwrites existing variables if these do not # use the "Constant" option: New-Variable test -value 100 -description "test variable" -force New-Variable : variable "test" may not be removed since it is a constant or write-protected. At line:1 Char:4 + del <<<< variable:\test -force You can overwrite an existing variable by using the -Force parameter of New-Variable if the existing variable wasn't created with the Constant option. this description appears to be invisible: # Create variable with description: New-Variable myvariable -value 100 -description "test variable" -force # Variable returns only the value: $myvariable 100 # Dir and Get-Variable also do not deliver the description: Dir variable:\myvariable . At line:1 Char:13 + New-Variable <<<< test -value 100 -description "test variable with copy protection" -option Constant # If existing variable is deleted.created with New-Variable. like the process-ID of the PowerShell console or the root directory. You'll then be able to turn writeprotection on and off for variables that already exist. The drive variable: provides you with an overview of all variables: Get-Childitem variable: Name Value -------Error {} DebugPreference SilentlyContinue PROFILE C:\Users\Tobias Weltner\Documents\WindowsPowerShell\Micro. you'll find out more about how write-protection works.. However. you can't modify them. must not be modified.. While you can read them. $pid = 12 Cannot overwrite variable "PID" because it is read-only or constant. If you set them to read-only.. All you need to do is add the prefix to the variable name: env:. Working with environment variables in PowerShell is just as easy as working with internal PowerShell variables.) You can show their description to understand the purpose of automatic variables: Get-Childitem variable: | Sort-Object Name | Format-Table Name.. This makes sense because information. PowerShell may stop and not respond to any inputs. At line:1 char:5 + $pid <<<< = 12 A little later in this chapter. Environment Variables There is another set of variables maintained by the operating system: environment variables. Description -AutoSize -Wrap Use Get-Help to find out more PowerShell write protects several of its automatic variables. .Name Value -------myvariable 100 Get-Variable myvariable Name Value -------myvariable 100 "Automatic" PowerShell Variables PowerShell also uses variables for internal purposes and calls those "automatic variables. HOME C:\Users\Tobias Weltner (.‖ These variables are available right after you start PowerShell since PowerShell has defined them during launch. One reason is because PowerShell continually modifies some variables. don't do this for automatic variables because PowerShell may crash. C:\Windows\System32\Wbem..JS. the variable behaves just like any other PowerShell variable.C:\Windows.. you can embed it in some text: "The Windows folder is here: $env:windir" The Windows folder is here: C:\Windows You can just as easily use the variable with commands and switch over temporarily to the Windows folder like this: # save in current folder: Push-Location # change to Windows folder cd $env:windir Dir # change back to initial location after executed task Pop-Location Searching for Environment Variables PowerShell keeps track of Windows environment variables and lists them in the env: virtual drive.COM...VBS.MSC.C:\ TEMP C:\Users\TOBIAS~1\AppData\Local\Temp ProgramData C:\ProgramData PATHEXT .BAT..WSF.4mm ALLUSERSPROFILE C:\ProgramData PUBLIC C:\Users\Public OS Windows_NT USERPROFILE C:\Users\Tobias Weltner HOMEDRIVE C: (..WSH.EXE.. In other word.. if you'd like an overview of all existing environment variables.VBE.. For example.CMD. So.) You‘ll be able to retrieve the information it contains when you‘ve located the appropriate environment variable and you know its name: $env:userprofile C:\Users\Tobias Weltner Modifying Environment Variables .. you can list the contents of the env: drive: Get-Childitem env: Name Value -------Path C:\Windows\system32. but in Windows environment variables.JSE... you‘ve told PowerShell not to look for the variable windir in the default PowerShell variable store..Reading Environment Variables You can read the location of the Windows folder of the current computer from a Windows environment variable: $env:windir C:\Windows By adding env:. $newValue. Modifying environment variables can be useful to change the way your machine acts. You no longer need to specify the complete path or a file extension. Any script you place into that folder will then be accessible simply by entering its name: # Create a special folder: md c:\myTools # Create and example script in this folder: " 'Hello!' " > c:\myTools\sayHello. You can easily check this: $env:Path The permanent change you just made applies only to you." You will need full administrator privileges to do that. For example. not just PowerShell sessions). PowerShell works with the so-called "process" set of environment variables. which get executed each time you launch PowerShell (then your changes are effective in any PowerShell session but not outside) or you can use sophisticated . you can replace the "User" argument by "Machine. The next example shows how you can create a new folder and add it to the PATH environment variable. "User") Access to commands of the . This code adds a path to the Path environment variable and the change is permanent. . the logged-on user. Changes to these environment variables will not persist and are discarded once you close your PowerShell session. They are just a copy and only valid inside your current PowerShell session (and any programs you launch from it). you would have to specify a qualified path name: C:\myTools\sayHello.ps1 # Typically.NET Framework as shown in this example will be described in depth in Chapter 6 When you close and restart PowerShell. $oldValue = [environment]::GetEnvironmentvariable("Path". the Path environment variable will now retain the changed value.You can modify environment variables by simply assigning new variables to them.ps1 Hello! # The folder is now added to the path environment: $env:path += ". If you‘d like this change to be in effect for all computer users. You have two choices if you need to make permanent changes to your environment variables. You can either make the changes in one of your profile scripts. all programs and scripts located in a folder that is listed in the "PATH" environment variable can be launched by simply submitting the file name.c:\myTools" [environment]::SetEnvironmentvariable("Path". "User") $newValue = ".C:\myTools" # All scripts and commands in this folder can be launched by entering their name now: sayHello Hello! Permanent Modifications of Environment Variables By default.NET methods directly to change the underlying original environment variables (in which case the environment variable change is visible to anyone. a script developer has even more control over scope by prefixing variable and function names. Scope allocation $private:test =1 $local:test = 1 Description The variable exists only in the current scope. So if a variable is defined in this scope. Scope of Variables PowerShell variables can have a "scope. the script‘s own scope is merged into the consol e scope. One is to call the script "dot-sourced": type in a dot." which determines where a variable is available. and then the path to the script.3: Variable scopes and validity of variables . $global:test = This scope represents the scope of the PowerShell console. but they cannot be modified. and (b) for library scripts whose purpose is to define functions and variables for later use. It cannot be accessed in any other scope. Table 3. Setting Scope While the user of a script can somewhat control scope by using dot-sourcing. Local variables can be read from scopes originating from the current scope. So when you run a script to do some task. The profile script. a script will use its own variable scope and isolate all of its variables from the console. All functions and parts of a script can share 1 variables by addressing this scope. Any function you define in any of your profile scripts will be accessible in your entire PowerShell session – even though the profile script is no longer running. private. Changing Variable Visibility You can change this default behavior in two different ways. it will not leave behind any variables or functions defined by that script once the script is done. it will leave behind all such variables and functions. and global. So when the script is done. then a space. Now.You should only change environment variables permanently when there is no other way. local. it 1 will still exist even when the script that is defining it is no longer running. local. Every top-level variables and functions defined in the script will behave as if they had been defined right in the console. is an example of a script that always runs dot-sourced. and script. Let's use the scope modifiers private. For most purposes. which launches automatically when PowerShell starts. These scopes allow you to restrict variable visibility in functions or scripts. Variables will be created only in the local scope. You can assign it the value of $null to remove a value. Automatic Restriction Typically. Dot-sourcing is used when you want to (a) debug a script and examine its variables and functions after the script ran. That's the default for variables that are specified without a scope. script. $script:test = This scope represents the top-level scope in a script. PowerShell supports four special variable scopes: global. it is completely sufficient to change the temporary process set from within PowerShell. then the child scope actually creates a completely new variable in its own scope. such as by defining a function: # Define test function: Function test { "variable = $a". $a = 1000 } # Create variable in console scope and call test function: $a = 12 Test variable = 12 # After calling test function. There are exceptions to this rule. If the variable already existed. Inside the console. a child scope can read the variables of the parent scope. you can manually change the variable . control modifications in console scope: $a 12 When you don't use any special scope prefix. Or." In this case. To change scope of an existing variable. all scopes are the same. as in the example above. The PowerShell console is the basic scope (global scope). $a = 1000 } # Create variable in console scope and call test function: $private:a = 12 Test variable = # Check variable for modifications after calling test function in console scope: $a 12 Only when you create a completely new variable by using $private: is it in fact private. you will need to first remove it and then recreate it: Remove-Variable a would remove the variable $a. If the child scope modifies a variable that was present in the parent scope. Here is a little walk-through. the script scope will merge with the caller‘s scope. Functions again create their own scope and functions defined inside of other functions create additional sub-scopes.Script blocks represent scopes in which variables and functions can live. PowerShell will not reset the scope. but not change them. If a parent scope declares a variable as "private. so prefixing a variable will not make much difference: $test = 1 $local:test 1 $script:test = 12 $global:test 12 $private:test 12 Differences become evident only once you create additional scopes. Each script launched from the console creates its own scope (script scope) unless the script is launched "dot-sourced. # Define test function: Function test { "variable = $a"." then it is accessible only in that scope and child scopes will not see the variable. and the parent scope's variable remains unchanged. Weakly typed variables will happily accept anything. It will tell you the data type PowerShell has picked to represent the data: (12). This process of automatic selection is called "weak typing.Name Decimal ("H"). PowerShell will stick to the generic string type. If a number is too large for a 32-bit integer.Name String (Get-Date). Call the method GetType(). it's also often restrictive or risky.Options = "Private.Name Int64 (12." and while easy. in practice. Often.GetType(). To find out what data types really are.5). then the Double data type best represents the data. Date and time values are stored in DateTime objects.GetType(). then the type will be preserved no matter what and will never be automatically changed to another data type. PowerShell uses the String data type. If it's a decimal number. PowerShell will not always pick the best data type. Strongly Typing You can enclose the type name in square brackets before the variable name to assign a particular type to a variable.GetType().options: (Get-Variable a). You can guarantee that the variable gets the information you expected by strongly typing a variable — or else will throw an exception that can alarm you. then there are better data types that will much better represent dates or IP addresses. You can be absolutely sure that a value of the correct type is stored in the variable.Name Double (12d). when you store data in a variable. Special variable types: When automatically assigning a variable type. you can use the Byte type: .Options = "None. even wrong pieces of information. Whenever you specify text. For text information. there are two important reasons for you to choose the data type yourself: Type safety: If you have assigned a type to a variable yourself. Instead. you can explore data types. So. Also." Variable Types and "Strongly Typing" Variables by default are not restricted to a specific data type. this will cause an exception. if you know that a particular variable will hold only numbers in the range 0 to 255. If someone later on wants to mistakenly assign a value to the variable that doesn't match the originally chosen type.GetType(). it's much better to store values in a specialized and more meaningful variable type like DateTime. PowerShell will automatically pick a suitable data type for you.Name Int32 (1000000000000). If the text you specified was really a date or an IP address. For example.GetType(). it switches to 64-bit integer." You can change a variable scope back to the initial default "local‖ by assigning (Get-Variable a).GetType().Name DateTime PowerShell will by default use primitive data types to store information. PowerShell will choose from generic variable types like Int32 or String. 10. a DateTime object can easily add and subtract days from a given date.10. 2004 If you store a date as String. but it will also raise an error if a value outside the range is specified: $flag = 300 The value "300" cannot be converted to the type "System.10. January 11. Only DateTime objects make them available. since the variable converted the text information into a specific DateTime object. So.10.AddDays(60) Tuesday. PC2} . XML documents will be much better represented using the XML data type then the standard String data type: # PowerShell stores a text in XML format as a string: $t = "<servers><server name='PC1' ip='10. 2004" $date November 12.10'/>" + "<server name='PC2' ip='10.12'/></servers>" $t <servers><server name='PC1' ip='10. 2004 00:00:00 Now. it's better to store it explicitly as DateTime: $date = "November 12.12'/></servers> # If you assign the text to a data type[xml]. if you're working with date and time indicators. if you're working with date and time information. it's better to store them explicitly as DateTime: [datetime]$date = "November 12.NET data types." At line:1 char:6 + $flag <<<< = 300 The Advantages of Specialized Types If you store a date as String.Name Byte The variable will now store your contents in a single byte. it tells you the day of the week and also enables specific date and time methods.10. Only DateTime objects offer all kinds of methods to deal with date and time information.Byte".10. 2005 00:00:00 PowerShell supports all.GetType().[Byte]$flag = 12 $flag. Error: "The value for an unsigned byte was too large or too small. So. For example.10'/> <server name='PC2' ip='10.10. November 12. then you'll have no access to special date functions. you'll # suddenly be able to access the XML structure: [xml]$list = $t $list. This will get you the date 60 days from the date you specified: $date.servers server -----{PC1.10. you'll have no access to special date functions. 2004" $date Friday. which is not only very memory-efficient. Guid]::NewGuid() $id.10. "lo") [sbyte]$value = -12 [single]$amount = 44..10.servers.10.10.10.server[0]. to implement optional parameters PowerShell object Regular expression 8-bit integers with characters PowerShell scriptblock Single-precision floating point number String PowerShell switch parameter Time interval Type Unsigned 16-bit integer Example [boolean]$flag = $true [byte]$value = 12 [char]$a = "t" [datetime]$date = "12.11" /> <server name="PC2" ip="10.12" /></servers> Variable Description type [array] An array [bool] Yes-no value [byte] Unsigned 8-bit integer. [float] [string] [switch] [timespan] [type] [uint16] Decimal number Double-precision floating point decimal Globally unambiguous 32-byte identification number Hash table 16-bit integer with characters 32-bit integers with characters 64-bit integers with characters Widens another data type to include the ability to contain null values.10.servers name ip ---.$list.45 [guid]$id = [System.Nov 2004 12:30" [decimal]$a = 12 $a = 12d $amount = 12.12 # The result could be output again as text.toString() [int16]$value = 1000 [int32]$value = 5000 [int64]$value = 4GB [Nullable``1[[System.Sep 07" [uint16]$value = 1000 .10.67 [string]$text = "Hello" [timespan]$t = New-TimeSpan $(Get-Date) "1. [int] [int64].get_InnerXML() <servers><server name="PC1" ip="10.10.12 # Even changes to the XML contents are possible: $list. It can be used.10 PC2 10. among others..server name ip ---. 0.DateTime]]]$test = Get-Date $test = $null $text = "Hello World" [regex]::split($text.10.11" $list.servers.ip = "10.255 [char] Individual unicode character [datetime] Date and time indications [decimal] [double] [guid] [hashtable] [int16] [int32].-PC1 10.11 PC2 10. [long] [nullable] [psobject] [regex] [sbyte] [scriptblock] [single].10.10. including the # modification: $list.-PC1 10.10.10.10. its contents). such as write-protection or AllScope.NET data types Variable Management: Behind the Scenes Whenever you create a new variable in PowerShell. Pipe the output to the cmdlet Select-Object to see all object properties and not just the default properties: $psvariable | Select-Object Name : testvariable Description : Value : Hello Options : None Attributes : {} Description: The description you specified for the variable. you'll need the underlying PSVariable object. Use either the cmdlet Set-Variable or directly modify the PSVariable object. For example.Description = "Subsequently added description" Dir variable:\test | Format-Table name. If you'd like to see the remaining information that was assigned to the variable. Modification of Variables Options One reason for dealing with the PSVariable object of a variable is to modify the variable's settings. description . Attributes: Additional features. Get-Variable will get it for you: $testvariable = "Hello" $psvariable = Get-Variable testvariable You can now display all the information about $testvariable by outputting $psvariable. If you retrieve a variable in PowerShell. but also other information. Options: Options that have been set. such as the description that you assigned to the variable or additional options like write-protection. it is stored in a PSVariable object.e.[uint32] [uint64] [xml] Unsigned 32-bit integer Unsigned 64-bit integer XML document [uint32]$value = 5000 [uint64]$value = 4GB Table 3. The brackets behind Attributes indicate that this is an array. PowerShell will return only the variable value. which can consist of several values that can be combined with each other.5: Commonly used . if you'd like to change the description of a variable. Value: The value assigned currently to the variable (i. This object contains not just the value of the variable. such as permitted data type of a variable for strongly typed variables. you can get the appropriate PSVariable object and modify its Description property: # Create new variable: $test = "New variable" # Create PSVariable object: $psvariable = Get-Variable test # Modify description: $psvariable. variable contents may now # be modified freely: $Example = 20 The Constant option must be set when a variable is created because you may not convert an existing variable into a constant. i." Dir variable:\test | Format-Table name. you do not need to store the PSVariable object in its own variable to access its Description property. Variables may only be set as constants when they are created.O <<<< options = "Constant" . you can use a sub-expression.Name Description ---.----------test Subsequently added description # Get PSVariable object and directly modify the description: (Get-Variable test).Options = "ReadOnly" # Modify option as wish with Set-Variable.----------test An additional modification of the description. description Name Description ---.Description An additional modification of the description. description Name Description ---.e. # A normal variable may not be converted into a constant: $constant = 12345 (Get-Variable constant). Instead. because the variable # is read-only.----------test Another modification As you can see in the example above. Reading the settings works only with the PSVariable object: (Get-Variable test).Description = "An additional modification of the description. You could have done the same thing by using Set-Variable. a statement in parentheses. you can add the ReadOnly option to a variable if you'd like to write-protect it: $Example = 10 # Put option directly in PSVariable object: (Get-Variable Example). -force is required: Set-Variable Example -option "None" -force # Write-protection turned off again. Write-Protecting Variables For example." At line:1 char:26 + (Get-Variable constant). PowerShell will then evaluate the contents of the sub-expression separately. # Modify a description of an existing variable with Set-Variable: Set-Variable test -description "Another modification" Dir variable:\test | Format-Table name. The expression directly returns the required PSVariable object so you can then call the Description property directly from the result of the sub-expression.Options = "Constant" Exception in setting "Options": "The existing variable "constant" may not be set as a constant. Attributes TypeId -----System. "AllScope" The variable is automatically copied in a new variable scope. In the following example.Clear() # Strong type specification is removed. At line:1 char:3 + $a <<<< = "Prohibited because its length is not from 2 to 8 .Management. This option must already be specified when the variable is "Constant" created. Once specified this option cannot be changed.Management.Option Description "None" NO option (default) "ReadOnly" Variable contents may only be modified by means of the -force parameter Variable contents can't be modified at all.Add($(New-Object ` System.Automation. .ArgumentTypeConverterAttribute # Delete type specification: (Get-Variable a).8)) $a = "Permitted" $a = "This is prohibited because its length is not from 2 to 8 characters" Because of an invalid value verification (Prohibited because its length is not from 2 to 8 characters) may not be carried out for the variable "a". now the variable can # store text again: $a = "Test" Validating Variable Contents The Attributes property of a PSVariable object can include additional conditions.ValidateLengthAttribute ` -argumentList 2. the variable will be unspecific again so in essence you remove the strong type again: # List attributes and delete: (Get-Variable a). Table 3. "Private" The variable is visible only in a particular context (local variable).6: Options of a PowerShell variable Examining Strongly Typed Variables Once you assign a specific data type to a variable as shown above. such as the maximum length of a variable.Automation.Attributes.Attributes. PowerShell will add this information to the variable attributes. a valid length from two to eight characters is assigned to a variable. An error will be generated if you try to store text that is shorter than two characters or longer than eight characters: $a = "Hello" $aa = Get-Variable a $aa. If you delete the Attributes property. Attributes. Restriction Variable may not be zero Variable may not be zero or empty Variable must match a Regular Expression Variable must match a particular number range Variable may have only a particular set value Table 3.100)) $age = 30 $age = 110 Because of an invalid value verification (110) may not be carried out for the variable "age"
[email protected] ` -argumentList $pattern)) $email = "
[email protected]. At line:1 char:7 + $age <<<< = 110 If you would like to limit a variable to special key values. ValidateSetAttribute is the right option. The variable $option accepts only the contents yes. use ValidateRangeAttribute. The e-mail address is defined by what is called a Regular Expression.[A-Z]{2.de" $email = "invalid@email" Because of an invalid value verification (invalid@email) may not be carried out for the variable "email"._%+-]+@[A-Z0-9.Add($(New-Object ` System.Attributes. there are additional restrictions that you can place on variables.Management. $email = "tobias.Automation.Attributes.Automation.NET object to the attributes with New-Object. or perhaps: $option = "yes" $v = Get-Variable option $v. the variable must contain a valid e-mail address or all values not matching an e-mail address will generate an error.In the above example Add() method added a new . no.4}\b" $v.com" $v = Get-Variable email $pattern = "\b[A-Z0-9.Add($(New-Object ` System. The variable $age accepts only numbers from 5 to 100: $age = 18 $v = Get-Variable age $v.Add($(New-Object ` System.ValidateSetAttribute ` Category ValidateNotNullAttribute ValidateNotNullOrEmptyAttribute ValidatePatternAttribute ValidateRangeAttribute ValidateSetAttribute .7: Available variable validation classes In the following example.ValidateRangeAttribute ` -argumentList 5.-]+\. Along with ValidateLengthAttribute.Management. At line:1 char:7 + $email <<<< = "invalid@email" If you want to assign a set number range to a variable.Automation. You'll learn more about New-Object in Chapter 6. You'll learn more about Regular Expressions in Chapter 13. 0. At line:1 char:8 + $option <<<< = "don't know" Summary Variables store information. characters.-argumentList "yes". They are called "automatic variables. by default PowerShell stores only the last 64 commands you ran (which you can list with Get-History or re-run with Invoke-History). 2. the variable $psversiontable will dump the current PowerShell version and versions of its dependencies: PS > $PSVersionTable Name Value -------CLRVersion 2.0.1 PSRemotingProtocolVersion 2.16385 PSVersion 2.1.7600.1). To make PowerShell remember more." These variables tell you information about the PowerShell configuration. It's easiest for you to set this special variable options by using the NewVariable or Set-Variable cmdlets (Table 3. you can restrict a variable to a specific data type of your choice.0 WSManStackVersion 2. PowerShell will automatically pick a suitable data type.50727. "perhaps")) $option = "no" $option = "perhaps" $option = "don't know" Verification cannot be performed because of an invalid value (don't know) for the variable "option". By strongly-typing variables. It contains settings that write-protect a variable or attach a description to it (Table 3.4952 BuildVersion 6.1. the variable name must be enclosed in brackets.6). If you'd like to use characters in variable names with special meaning to PowerShell (like parenthesis). and special characters. and once you assign a value to a variable. For example.0 PSCompatibleVersions {1. Variables are not case-sensitive. There are pre-defined variables that PowerShell will create automatically.0} SerializationVersion 1. "no". PowerShell doesn't require that variables be specifically created or declared before use.1 You can change the way PowerShell behaves by changing automatic variables. beginning with PowerShell 2.0. . such as the underline character "_". You strongly-type a variable by specifying the data type before the variable name: # Strongly type variable a: [Int]$a = 1 You can prefix the variable name with "$" to access a variable. The variable name can consist of numbers. Variables are by default not bound to a specific data type. For example.0. just adjust the variable $MaximumHistoryCount: PS > $MaximumHistoryCount 64 PS > $MaximumHistoryCount = 1000 PS > $MaximumHistoryCount 1000 PowerShell will store variables internally in a PSVariable object. . . . : Medium disconnected Connection-specific DNS Suffix: Connection location IPv6 Address . . private:. : fe80::6093:8889:257e:8d1%8 IPv4 address .255. . PowerShell will automatically wrap the results into an array. . : 192. you might at first think that the variable contains plain text: $a = ipconfig $a Windows IP Configuration Ethernet adapter LAN Connection Media state . . script:. . . . . .1.168. So dealing with arrays is important in PowerShell. . ." Topics Covered: PowerShell Commands Returns Arrays o Discovering Arrays o Processing Array Elements in a Pipeline o Working with Real Objects Creating New Arrays o Polymorphic Arrays o Arrays With Only One (Or No) Element Addressing Array Elements o Choosing Several Elements from an Array o Adding Elements to an Array and Removing Them Using Hash Tables o Creating a New Hash Table o Creating Objects From Hash Tables o Using Hash tables To Calculate Properties o Storing Arrays in Hash Tables o Inserting New Keys in an Existing Hash Table o Modifying and Removing Values Copying Arrays and Hash Tables Strongly Typed Arrays Summary PowerShell Commands Return Arrays If you store the result of a command in a variable and then output it. . PowerShell accesses the variable in the current scope. . . you will learn how arrays work. an initial variable scope is created.0 Standard Gateway . .1 In reality. and PowerShell returns them as an array. . This occurs automatically whenever a command returns more than a single piece of data. : 255. and every script and every function will create their own scope. . . but you can specify other scopes by adding a prefix to the variable name\: local:. . When PowerShell starts. . Whenever a command returns more than one result. In this chapter.1. . . . .168. . We will cover simple arrays and also so-called "associative arrays. . ." which are also called "hash tables. . : 192. . . . . the result consists of a number of pieces of data. . . .35 Subnet Mask . . and global:. .Every variable is created in a scope. . By default.255. Only when a command returns more than one result will it wrap them in an array. you can specify its index number. If you‘d like to examine a single array element.Count 53 Here. or many results. this will make writing scripts difficult because sometimes you cannot predict whether a command will return one. Use @() if you'd like to force a command to always return its result in an array. $result = Dir $result -is [array] True $result = Dir C:\autoexec.Discovering Arrays You can check the data type to find out whether a command will return an array: $a = "Hello" $a -is [Array] False $a = ipconfig $a -is [Array] True An array will always supports the property Count. none. the ipconfig command returned 53 single results that were all stored in $a. If an array has 53 elements. If a command returns just one result.Count Processing Array Elements in a Pipeline .Count Or in a line: $result = @(Dir $env:windir -ea 0). then its valid index numbers are 0 to 52 (the index always starts at 0). This way you find out the number of files in a folder: $result = @(Dir $env:windir -ea 0) $result. it will happily return that exact result to you.bat $result -is [array] False Of course. That's why you can make PowerShell return any result as an array. which will return the number of elements stored in that array: $a. # Show the second element: $a[1] Windows IP Configuration It is important to understand just when PowerShell will use arrays. With minimal effort.Count 82 Every element in an array will represent a file or a directory. you can filter out unwanted text lines: # Store result of an array and then pass along a pipeline to Select-String: $result = ipconfig $result | Where-Object { $_ -like "*Address*" Connection location IPv6 Address . .-----. This is great since all the text lines are individual array elements. .) Let's check if the return value is an array: $result = Dir $result. this individual information will consist of plain text. not text.1.10.PowerShell. : fe80::14ab:a532:a7b9:cd3a%11 As such. . So if you output an element from the array to the console.1. .. .06/28/2007 18:33 Debug d-r-. the result of ipconfig was passed to Where-Object.Ipconfig will return each line of text as an array element. .10/04/2007 14:21 Desktop d-r-. : fe80::5efe:192. which filtered out all text lines that did not contain the keyword you were seeking.---d-r-.Core\FileSystem::C:\Users\ Tobias Weltner Mode LastWriteTime Length Name ---. While it is a command that will return individual information stored in arrays. allowing you to process them individually in a pipeline.10/09/2007 12:21 Downloads (.Core\FileSystem::C:\Users\ Tobias Weltner Mode LastWriteTime Length Name ---. not a PowerShell cmdlet. Real PowerShell cmdlets will return rich objects. .10/04/2007 21:23 Documents d-r-.07/26/2007 11:03 Backup d-r-.35 Connection location IPv6 Address ..------------.04. : fe80::6093:8889:257e:8d1%8 IPv4 address .2007 14:21 Desktop . For example. .---d---.35%16 Connection location IPv6 Address . . .10/01/2007 16:09 Application Data d---.168. . . you can now reduce the results of ipconfig to the information you deem relevant. .04/13/2007 15:05 Contacts d---. : 192.------------. Working with Real Objects Ipconfig is a legacy command.168.PowerShell. . even though the results will appear as plain text: Dir Directory: Microsoft.-----. PowerShell will automatically convert the object into text: # Access the fifth element: $result[4] Directory: Microsoft. Core\FileSystem PSIsContainer : True Mode : d-r-Name : Desktop Parent : Tobias Weltner Exists : True Root : C:\ FullName : C:\Users\Tobias Weltner\Desktop Extension : CreationTime : 04/13/2007 01:54:53 CreationTimeUtc : 04/12/2007 23:54:53 LastAccessTime : 10/04/2007 14:21:20 LastAccessTimeUtc : 10/04/2007 12:21:20 LastWriteTime : 10/04/2007 14:21:20 LastWriteTimeUtc : 10/04/2007 12:21:20 Attributes : ReadOnly. Directory You'll learn more about these types of objects in Chapter 5.4 $array 1 2 3 4 . you can pipe them on to Select-Object and specify an "*" to show all properties. each element returned by Dir (Get-Childitem) is really an object with a number of individual properties. PowerShell will now output them as list rather than table since the console is too narrow to show them all # Display all properties of this element: $result[4] | Format-List * PSPath : Microsoft. Creating New Arrays You can easily create your own arrays.PowerShell. Length..PowerShell. LastWriteTime. To see all object properties.In reality.Core\FileSystem:: C:\Users\Tobias Weltner PSChildName : Desktop PSDrive : C PSProvider : Microsoft.PowerShell. though.2. Some of these properties surfaced in the previous example as column headers (like Mode.3. and Name). Simply use a comma to place elements into an array: $array = 1. The majority of properties did not show up.4 $array 1 2 3 4 There's even a shortcut for number ranges: $array = 1.Core\FileSystem:: C:\Users\Tobias Weltner\Desktop PSParentPath : Microsoft. . Parentheses will identify a sub-expression and tell PowerShell to evaluate and process it first.. individual elements of an array can store any type of value you assign. Again. 2. Arrays can only store data.2. August 21. 1.Length 1 You'll need to use the construct @(."Hello") $array 1 2 3 Hello Why would you want to create an empty array in the first place? Because you can add elements to it like this when you start with an empty array: $array = @() $array += 1 $array += 3 1 3 Addressing Array Elements . you can store whatever you want in an array. 2007 12:12:28 Why is the Get-Date cmdlet enclosed in parentheses? Just try it without parentheses. Arrays With Only One (Or No) Element How do you create arrays with just one single element? Here's how: $array = . you can separate the elements by using commas: $array = "Hello". This way. Get-Date is a command and no data. even a mixture of different data types. (Get-Date) $array Hello World 1 2 Tuesday.)to create an array without any elements at all: $array = @() $array. you will need to use parentheses.Polymorphic Arrays Just like variables.3.1 $array. Since you want PowerShell to evaluate the command first and then put its result into the array.Length 0 $array = @(12) $array 12 $array = @(1. "World". The index -1 will always give you the last element in an array. assume you have a path and want to access only the file name. You can also use expressions that calculate the index value: # Create your own new array: $array = -5.length-1] 12 # Access a dynamically generated array that is not stored in a variable: (-5. Both of these properties will behave identically. First.Split('\') PS > $array c: folder subfolder file. the first element in your array will always have the index number 0. Here is a real-world example using arrays and accessing individual elements. Every string object has a built-in method called Split() that can split the text into chunks. you can change the split character and use ". You will find that negative index numbers count from last to first.txt" PS > $array = $path.Every element in an array is addressed using its index number. you will access the last array element: PS > $array[-1] file..12)[2] -3 Remember. The result is a new array that contains the subset that you picked from the original array: . The file name is always the last element of that array.txt As you see. by splitting a path at the backslash.Count-1] 12 $array[$array. So to access the filename.12 # Access the first element: $array[0] -5 # Access the last element (several methods): $array[-1] 12 $array[$array." instead: PS > $path.')[-1] txt Choosing Several Elements from an Array You can also access more than one array element at once by specifying multiple index numbers. The example demonstrates that the total number of all elements will be returned in two properties: Count and Length.Split('.. if you are interested in the file name extension. All you will need to do is submit the split character that is used to separate the chunks: PS > $path = "c:\folder\subfolder\file. you will get its components.txt Likewise. You'll have to make a new copy of the array with a new size to add or remove elements later.------------..08/12/2007 10:21 Favorites d-r-..NET Framework (see Chapter 6). 8th.07/26/2007 11:03 Backup d-r-.PowerShell.04/13/2007 01:55 Saved Games The second line will select the second. and 13th entry: $list[1.. You can use this approach to reverse the contents of an array: # Create an array with values from 1 to 10 $array = 1. you can use the special array functions of the . fifth. You can simply use the "+=" operator to do that and then add new elements to an existing array: # Add a new element to an existing array: $array += "New Value" $array 1 2 3 New Value .---d---.# Store directory listing in a variable: $list = dir $home # Output only the 2nd.-----.4.Core\FileSystem::C:\Users\ Tobias Weltner Mode LastWriteTime Length Name ---. 5th. Instead.0] $array 10 9 . 1 Reversing the contents of an array using the approach described above is not particularly efficient because PowerShell has to store the result in a new array. eighth. and thirteenth elements (remember that the index begins at 0).. This will enable you to reverse the contents of an array very efficiently: # Create an array containing text and output contents: $a = ipconfig $a # Reverse array contents and then output it again: [array]::Reverse($a) $a Adding Elements to an Array and Removing Them Arrays will always contain a fixed number of elements.08/20/2007 07:52 Desktop d-r-.12] Directory: Microsoft.7.10 # Select the elements from 9 to 0 (output array contents # in reverse order): $array = $array[($array.length-1). Here. remove. larger array.Count 9 As you can imagine.You will find that array sizes can't be modified so PowerShell will work behind the scenes to create a brand-new.RemoveAt(3) PS > $superarray. which is a specialized array. You can use it as a replacement for regular arrays and benefit from the added functionality..Collections.Sort() PS > $superarray 1 2 3 5 6 7 8 9 10 11 100 PS > $superarray. A much more efficient way is to convert an array to an ArrayList object. creating new arrays to add or remove array elements is a slow and expensive approach and is only useful for occasional array manipulations. For example.10] $array.Add(11) | Out-Null PS > $superarray. insert or even sort array contents: PS > $array = 1. which makes it easy to add.ArrayList]$array PS > $superarray.Insert(2. the next line will remove elements 4 and 5 using the indexes 3 and 4: $array = $array[0. PowerShell will work exactly the same way when you want to delete elements from an array. the original array is copied to a new.100) PS > $superarray 1 2 100 3 5 6 7 8 9 10 11 PS > $superarray. smaller array while disposing of the old array.. too.Reverse() PS > $superarray 100 11 10 9 8 7 6 5 ..10 PS > $superarray = [System. and adding the new element. copying the contents of the old array into it.2] + $array[5. 10. in hash tables you do not use a numeric index to address individual elements.10. User="Tobias Weltner"} Name Value ---. you can use @{} instead of @().10 # As for arrays.10.10.10.10 Tobias Weltner The example shows that you how to retrieve the values in the hash table using the assigned key.IP 10. several elements can be selected at the same time: $list["Name".10.10". IP="10. .10. Creating a New Hash Table When creating a new hash table.----Name PC01 IP 10. and specify respectively the key name with the value you want to return. There are two forms of notation you can use to do this: Square brackets: Either you use square brackets." So.10 # A key can also be specified by dot notation: $list.$key 10.10 # A key can even be stored in a variable: $key = "IP" $list. "IP"] PC01 10.10.keys] PC01 10. like in arrays.10.3 2 1 Using Hash Tables Hash tables store "key-value pairs. The key name can be specified as a variable. like with objects.10.10. You can use semi-colons to separate key-value pairs: # Create a new hash table with key-value pairs $list = @{Name = "PC01". but rather the key you assigned to a value.10.10 # Keys returns all keys in the hash table: $list.10.10 User Tobias Weltner # Access to the key "IP" returns the assigned value: $list["IP"] 10.10. Dot notation: Or you use dot notation.keys Name IP User # If you combine this. and specify the key-value pair that is to be stored in your new hash table. you can output all values in the hash table $list[$list. 0 Using Hash Tables to Calculate Properties Another scenario where hash tables are used is to calculate properties that do not exist." "Name" will hold the name of the calculated property. Creating Objects From Hash Tables One area where hash tables are used is when you want to return text results into real objects.The square brackets can return several values at the same time exactly like arrays if you specify several keys and separate them by a comma. this will only work when you create the hash table with initial values like in the example above. you can then pass it a hash table with formatting details: Expression: The name of object property to be displayed in this column Width: Character width of the column Label: Column heading Alignment: Right or left justification of the column You can just define a hash table with the formatting information and pass it on to Format-Table: . Expression={ if ($_.Length / 1MB) } else { 'n/a'} }} You can now use your hash table to add the calculated property to objects: Dir $env:windir | Select-Object Name. create a hash table and then convert this into an object. For example.psversion.1. $MBSize Note: Because of a PowerShell bug.0} MB' -f ($_. LastWriteTime. if you'd like to display file size in Megabytes instead of bytes. you can create a hash table with the keys "Name" and "Expression. First.6040000 PowerShellVersion ----------------2. For example. if you use Format-Table.BIOSVersion = Get-WmiObject Win32_BIOS | Select-Object ExpandProperty Version PS> $info. Hash tables can control even more aspects when using them in conjunction with the family of Format-* cmdlets.OperatingSystemVersion = Get-WmiObject win32_OperatingSystem | Select -Object -ExpandProperty Version PS> $info. and "Expression" will define a script block used to calculate the property: $MBSize = @{Name='Size (MB)'.7600 BIOSVersion ----------SECCSD . It will not work when you first create an empty hash table and then add the key-value pairs in a second step.ToString() PS> New-Object PSObject -property $info OperatingSystemVersion ---------------------6. Here is how you can do this: PS> $info = @{} PS> $info. Let's say you want to combine information you retrieved from different sources into one consolidated result object.PowerShellVersion = $PSVersionTable. Note that the key names in square brackets must be enclosed in quotation marks (you don't have to do this if you use dot notation).Length -ne $null) {'{0:0. 10.. label="last modification". width=30. $column2 File Name last modification ----------------------Application data 10/1/2007 16:09:57 Backup 07/26/2007 11:03:07 Contacts 04/13/2007 15:05:30 Debug 06/28/2007 18:33:29 Desktop 10/4/2007 14:21:20 Documents 10/4/2007 21:23:10 (.value1 12 $test.) You'll learn more about format cmdlets like Format-Table in the Chapter 5 Storing Arrays in Hash Tables You can store classic array inside of hash tables. which leaves the comma available to create classic arrays: # Create hash table with arrays as value: $test = @{ value1 = 12.10".. alignment="right"} # Output contents of a hash table: $column1 Name Value -------alignment left label File name width 30 expression Name # Output Dir command result with format table and selected formatting: Dir | Format-Table $column1. too.# Setting formatting specifications for each column in a hash table: $column1 = @{expression="Name".3 } # Return values (value 2 is an array with three elements): $test. # Create a new hash table with key-value pairs $list = @{Name = "PC01". IP="10. This is possible because hash tables use the semi-colon as keyvalue pair separators.2. you can just specify the new key and the value that is to be assigned to the new key. label="filename". you can choose between the square brackets and dot notations. width=40. Again. User="Tobias Weltner"} . value2 = 1.10. alignment="left"} $column2 = @{expression="LastWriteTime".value2 1 2 3 Inserting New Keys in an Existing Hash Table If you'd like to insert new key-value pairs in an existing hash table. ----Name PC01 Location New York Date 08/20/2007 13:10:12 IP 10.Location = "New York" Name Value ---..10 User Tobias Weltner If you'd like to completely remove a key from the hash table.remove("Date") Using Hash Tables for Output Formatting An interesting use for hash tables is to format text.# Insert two new key-value pairs in the list (two different notations are possible): $list.10.AddDays(-1) $list. just overwrite the value: # Overwrite the value of an existing key with a new value (two possible notations): $list["Date"] = (Get-Date).Name = "PC01" $list.Location = "Hanover" (.. use Remove() and as an argument specify the key that you want to remove: $list.10.10. Normally.10.10 Tobias Weltner You can create empty hash tables and then insert keys as needed because it's easy to insert new keys in an existing hash table: # Create empty hash table $list = @{} # Subsequently insert key-value pairs when required $list. PowerShell outputs the result of most commands as a table and internally uses the cmdlet Format-Table: .Date = Get-Date $list["Location"] = "Hanover" # Check result: $list Name ---Name Location Date IP User Value ----PC01 Hanover 08/21/2007 13:00:18 10.) Modifying and Removing Values If all you want to do is to change the value of an existing key in your hash table. but may produce unexpected results.) You'll learn more about format cmdlets like Format-Table in the Chapter 5. This enables you to control how the result of the command is formatted. When you work with arrays and hash tables.----alignment left label File name width 30 expression Name # Output Dir command result with format table and # selected formatting: Dir | Format-Table $column1. alignment="left"} $column2 = @{expression="LastWriteTime". you can pass it a hash table with formatting specifications. width=40. So. which always store only a single value. not the array or the hash table. Every column is defined with its own hash table.# Both lines return the same result: Dir Dir | Format-Table If you use Format-Table.. ` label="filename". ` label="last modification". only the reference will be copied. if you copy the contents of a variable to another. width=30. alignment="right"} # Output contents of a hash table: $column1 Name Value ---. $column2 File Name Last Modification --------. Copying Arrays and Hash Tables Copying arrays or hash tables from one variable to another works. values are assigned to the following four keys: Expression: The name of object property to be displayed in this column Width: Character width of the column Label: Column heading Alignment: Right or left justification of the column All you need to do is to pass your format definitions to Format-Table to ensure that your listing shows just the name and date of the last modification in two columns: # Setting formatting specifications for each column in a hash table: $column1 = @{expression="Name". In the hash table. The reason is that arrays and hash tables are not stored directly in variables. That could result in the following unexpected behavior: ..--------------Application Data 10/1/2007 16:09:57 Backup 07/26/2007 11:03:07 Contacts 04/13/2007 15:05:30 Debug 06/28/2007 18:33:29 Desktop 10/4/2007 14:21:20 Documents 10/4/2007 21:23:10 (. you are dealing with a reference to the array or hash table. If you want to limit the type of data that can be stored in an array. A new array is created in the process and stored in $array2: $array2 += 4 $array2[0]=99 # $array1 continues to point to the old array: $array1[0] 1 Strongly Typed Arrays Arrays are typically polymorphic: you can store any type of value you want in any element. The variables $array1 and $array2 internally reference the same storage area. You should specify the desired variable type in square brackets. You also specify an open and closed square bracket behind the variable type because this is an array and not a normal variable: # Create a strongly typed array that can store whole numbers only: [int[]]$array = 1. Error: "Input string was not in a correct format. Therefore.$array1 = 1. because they are both identical. an error # will be reported: $array += "Hello" The value "Hello" cannot be converted into the type "System. The following example clearly shows the consequences: # Create array and store pointer to array in $array2: $array1 = 1. use "strong typing" and predefine a particular type.2. this affects $array1 as well. you have to create a copy if you want to copy arrays or hash tables.3 # Everything that can be converted into a number is allowed # (including strings): $array += 4 $array += 12.2." .Int32".: $array1 = 1.2.56 $array += "123" # If a value cannot be converted into a whole number. PowerShell automatically selects the appropriate type for each element.3 $array2 = $array1 # Assign a new element to $array2.Clone() $array2[0] = 99 $array1[0] 1 Whenever you add new elements to an array (or a hash table) or remove existing ones.3 $array2 = $array1.3 $array2 = $array1 $array2[0] = 99 $array1[0] 99 Although the contents of $array2 were changed in this example. a copy action takes place automatically in the background and its results are stored in a new array or hash table.2. one command will hand over its result to the next. you receive the result. The PowerShell pipeline chains together a number of commands similar to a production assembly. and at the end. You can address single elements of an array or hash able by using square brackets. .key2=value2.1: Typical pipeline cmdlets and functions o Streaming: Real-time Processing or Blocking Mode? o "Blocking" Pipeline Commands Converting Objects into Text o Making Object Properties Visible o Formatting Pipeline Results o Displaying Particular Properties o Using Wildcard Characters o Scriptblocks and "Aggregate" Properties o Changing Column Headings o Optimizing Column Width Sorting and Grouping Pipeline Results o Sort Object and Hash Tables o Grouping Information o Using Grouping Expressions Filtering Pipeline Results o Limiting Number of Objects Analyzing and Comparing Results o Statistical Calculations Exporting Pipeline Results o Suppressing Results o HTML Outputs The PowerShell pipeline chains together a number of commands similar to a production assembly. Hash tables in contrast use a key name. an error will be reported. .. Element2. Specify either the index number (for arrays) or the key (for hash tables) of the desired element in the square brackets. .. You create new arrays with @(Element1. If you try to store values in it that cannot be turned into whole numbers. the array is able to store only whole numbers. So... Summary Arrays and hash tables can store as many separate elements as you like. You can also leave out @() for arrays and only use the comma operator. Using this approach you can select and retrieve several elements at the same time.). $array was defined as an array of the Integer type. Arrays assign a sequential index number to elements that always begin at 0. Topics Covered: Using the PowerShell Pipeline o Object-oriented Pipeline o Text Not Converted Until the End o Table 5. you will receive the result. one command hands over its result to the next.At line:1 char:6 + $array <<<< += "Hello" In the example.). Now. Semi-colons by themselves are not sufficient to create a new hash table. So. and at the end. You create new hash tables with @{key1=value1. @{} must always be specified for hash tables. That's why every element in hash tables consists of a key-value pair. the real pipeline benefits show only when you start adding more commands. Text Not Converted Until the End The PowerShell pipeline is always used. which then passes its result to Sort-Object. you would have had to tell Sort-Object just where the file size information was to be found in the raw text. All of this can start with a Dir command.NET objects through the pipeline. Length | ConvertTo-Html | Out-File report. the PowerShell pipeline will take an object-oriented approach and implement it in real time.htm It returns an HTML report on the windows directory contents sorted by file size. Take a look at Sort-Object.txt | Sort-Object . It will sort the directory listing by file size. which converts the resulting objects into text at the end of the pipeline. If the pipeline had simply fed plain text into Sort-Object. The sorted result will then get limited to only the properties you want in the report. You only have to tell Sort-Object which object‘s property to use for sorting because PowerShell will send results as rich . which is then written to a file.htm . Only at the end of the pipeline will the results be reduced to text or HTML or whatever you choose for output. PowerShell will attach to your input the cmdlet Out-Default. All you need to do is tell Sort-Object which object‘s property you want to sort. Have a look: Dir | Sort-Object Length | Select-Object Name. The following command will output only a directory's text files listing in alphabetical order: Dir $env:windir *. Not so here. which would then present the data screen page by screen page: Dir | more In contrast to the traditional concept of text piping. You would also have had to tell Sort-Object to sort this information numerically and not alphabetically. The chaining of several commands will allow you to use commands like Lego building blocks to assemble a complete solution from single commands. Even a simple Dir command is appended internally and converted into a pipeline command: Dir $env:windir | Out-Default Of course.\report. such as Name or LastWriteTime.Using the PowerShell Pipeline Command chains are really nothing new. Unlike text. Object-oriented Pipeline What you see here is a true object-oriented pipeline so the results from a command remain rich objects. information in an object is clearly structured: this is a crucial PowerShell pipeline advantage. One of the more known usages was to pipe data to the tool more. even when you provide only a single command. ConvertTo-Html will convert the objects to HTML. Simply replace Length with another object‘s property. The old console was able to forward (or "pipe") the results of a command to the next with the "pipe" operator "|". Sort-Object does the rest automatically. The object nature tells SortObject all it needs to know: where the information you want to sort is found and whether it is numeric or letters. to sort according to these criteria. Just make sure that the commands you use in a pipeline actually do process information from the pipeline. the following line is really useless because notepad. That's why there are two pipeline modes: .exe cannot process pipeline results: Dir $env:windir | Sort-Object | notepad If you'd like to open pipeline results in an editor. While it is technically OK.txt Cmdlet/Function Description Compare-Object Compares two objects or object collections and marks their differences ConvertTo-Html Converts objects into HTML code Export-Clixml Saves objects to a file (serialization) Export-Csv Saves objects in a comma-separated values file ForEach-Object Returns each pipeline object one after the other Format-List Outputs results as a list Format-Table Outputs results as a table Format-Wide Outputs results in several columns Get-Unique Removes duplicates from a list of values Group-Object Groups results according to a criterion Import-Clixml Imports objects from a file and creates objects out of them (deserialization) Measure-Object Calculates the statistical frequency distribution of object values or texts more Returns text one page at a time Out-File Writes results to a file Out-Host Outputs results in the console Out-Host -paging Returns text one page at a time Out-Null Deletes results Out-Printer Sends results to printer Out-String Converts results into plain text Select-Object Filters properties of an object and limits number of results as requested Sort-Object Sorts results Tee-Object Copies the pipeline's contents and saves it to a file or a variable Where-Object Filters results according to a criterion Table 5.The cmdlets in Table 5. notepad result.1 have been specially developed for the pipeline and the tasks frequently performed in it.1: Typical pipeline cmdlets and functions Streaming: Real-time Processing or Blocking Mode? When you combine several commands in a pipeline. They will all be demonstrated in the following pages of this chapter.txt. you'll want to understand when each separate command will actually be processed: consecutively or at the same time? The pipeline will process the results in real time. you can put the results in a file first and then open the file with the editor: Dir $env:windir | Sort-Object | Out-File result. at least when the commands chained together in the pipeline support real-time processing. com is also a blocking command that could interrupt pipeline streaming: # If the preceding command can execute its task quickly. If you use commands that are not designed for PowerShell then their authors had no way to implement the special demands of PowerShell. the longer the wait time will be for you. pipeline commands are executed one at a time. the memory space requirement is very high. Those results eventually block too much memory for your system to handle. That's not all. it may encounter sub-directories where you have no access rights. That is why Sort-Object is an example of a "blocking" pipeline command. confusing error messages may pile up. you may even run out of memory.com . Whether a command supports streaming is up to the programmer. it can take several minutes. The more data that has to be acquired. and because Sort-Object can only sort the results after all of them are available. If you let the command run too long. The sequential mode basically corresponds to the variable concept that first saves the result of a command to a variable before forwarding it to the next command. and only one element is travelling the pipeline at a time. For example. there are technical reasons why this command must first wait for all results. it's even higher so that the entire Windows system will respond more and more clumsily until finally you won't be able to control it any longer. These results are passed by the pipeline to Sort-Object. but only one single result at a time. error messages are not collected by Sort-Object and appear immediately. Otherwise. # you may not notice that it can be a block: Dir | more. Streaming Mode (quick): The streaming mode immediately processes each command result. it wouldn't be able to sort the results. If you have Dir output a complete recursive folder listing. Every single result is passed directly onto the subsequent command. The pipeline doesn't have to store all of the command's results. you won't see any signs of life from PowerShell for a long time. It will rush through the entire pipeline and is immediately output. Error messages and results get out of sync and may be misinterpreted. While Sort-Object continues to collect results (so no results appear). but more. Here Dir returns all files and directors on your drive C:\. In the above example. Sequential (slow) mode: In sequential mode. For Sort-Object. "Blocking" Pipeline Commands Sorting can only take place when all results are available. This mode is slow and hogs memory because results are returned only after all commands finish their work and the pipeline has to store the entire results of each command. In this specific case.com to output information one page at a time. This quick mode saves memory because results are output while the pipeline commands are still performing their tasks. Second problem: Because enormous amounts of data have to be stored temporarily before Sort-Object can process them. it will work if you use the traditional command more. The two problem areas in sequential mode are: First problem: You won't see any activity as long as data is being collected. This also means there can be long processing times and it can even cause instability if you don't pay attention to memory requirements: # Attention: danger! Dir C:\ -recurse | Sort-Object If you execute this example. which will first collect all data before it hands over the sorted result to the next command. it will collect the results as they come in. So the command's results are passed on to the next one only after the command has completely performed its task. In this case. not objects. while results stay rich data objects while traveling the pipeline. PowerShell developers forgot to add streaming support to the integrated more function.com # If the preceding command requires much time. This is done by (internally) adding Out-Default to your input. The following commands are identical: Dir Dir | Out-Default Out-Default will transform the pipeline result into visible text.com Tip: Use Out-Host -Paging instead of more! Out-Host is a true PowerShell cmdlet and will support streaming: Dir c:\ -recurse | Out-Host -paging Converting Objects into Text At the end of a day. So. its blocking action may cause issues: Dir c:\ -recurse | more. Dir | Format-Table * PSPat PSPar PSChi PSDri PSPro PSIsC Mode Name Pare Exis Root Full Exte Crea Crea Last Last Last Last Attr h entPa ldNam ve vider ontai nt ts Name nsio tion tion Acce Acce Writ Writ ibut th e ner n Time Time ssTi ssTi eTim eTim es Utc me meUt e eUtc . This is why more essentially doesn't behave much differently than the ancient more. this is what happens internally: Dir | Format-Table | Out-Host Making Object Properties Visible To really see all the object properties and not just the ones that PowerShell "thinks" are important. you want commands to return visible results. you may not notice that it can be # a block: Dir | more. they must be converted into text. or scripts can block pipelines if the programmer doesn't use streaming.com command: # If the preceding command can execute its task quickly. To do so. it will first call Format-Table (or FormatList when there are more than five properties to output) internally. # its blocking action may cause issues: Dir c:\ -recurse | more. Out-Host will output the text in the console. you can use Format-Table and add a "*" to select all object properties.# If the preceding command requires much time. functions. So. Surprisingly. at the end of the pipeline. followed by Out-Host.com But also genuine PowerShell cmdlets. 2.----...---.. True d... De......... D... 2. 2. A.2007 23:54:53 10. T.---. whenever there are more than five properties to display. Mi.y Mi.. 3..---. 2.---... Debug C Mi....----..---Mi. C Mi.. C. T.. C.. True C:\ 2.2007 19:37:26 ReadOnly.. True d..----.....---. . 2.... 1.04.2007 21:37:26 10.... Mi. 2. 1. Mi...... True C:\ 2. C.PowerShell.. 3.... T. . 2.. True C:\ 1.... 2...... Mi. 3. .c ----.---.C. True C:\ 2..y Mi. like this: Dir | Format-Table * -wrap Still...... 1... the horizontal table design is unsuitable for more than just a handful of properties.. .2007 19:37:26 10....05. T.. Co..... 1..-----... 2..... instead of Format-Table....2007 01:54:53 12. This is why PowerShell will use Format-List. You now get so much information that columns shrink to an unreadable format... you can use the -Wrap parameter..... 2. .. 2. True d. C.. it might look like this: PSPath Weltner\Music PSParentPath Weltner PSChildName PSDrive PSProvider PSIsContainer Mode Name Parent Exists Root FullName Extension CreationTime CreationTimeUtc LastAccessTime LastAccessTimeUtc LastWriteTime LastWriteTimeUtc Attributes : Microsoft.y Mi.----.. Ba..04..Core\FileSystem::C:\Users\Tobias : Microsoft.05... 2. True C:\ 1. 2. 3.. C Mi......... T.. True d.... For example.. if you'd prefer not to reduce visual display because of lack of space. B.y ---. 2...05... C. 1......... Directory .. 1... You should do the same: Dir | Format-List * You will now see a list of several lines for each object's property. D.---.PowerShell. C Mi.PowerShell...y Mi.05. 2........---. Ap...2007 21:37:26 10.. C Mi....Core\FileSystem True d-r-Music Tobias Weltner True C:\ C:\Users\Tobias Weltner\Music 13...Core\FileSystem::C:\Users\Tobias : : : : : : : : : : : : : : : : : : Music C Microsoft.----.---... For a folder... True d..---. Mi.. A file has slightly different properties: PSPath Weltner\views.PS1 PSParentPath Weltner PSChildName PSDrive PSProvider PSIsContainer Mode Name Length DirectoryName Directory IsReadOnly Exists FullName Extension CreationTime CreationTimeUtc LastAccessTime LastAccessTimeUtc LastWriteTime LastWriteTimeUtc Attributes : Microsoft.PowerShell.Core\FileSystem::C:\Users\Tobias : Microsoft.PowerShell.Core\FileSystem::C:\Users\Tobias : : : : : : : : : : : : : : : : : : : : views.PS1 C Microsoft.PowerShell.Core\FileSystem False -a--views.PS1 4045 C:\Users\Tobias Weltner C:\Users\Tobias Weltner False True C:\Users\Tobias Weltner\views.PS1 .PS1 18.09.2007 16:30:13 18.09.2007 14:30:13 18.09.2007 16:30:13 18.09.2007 14:30:13 18.09.2007 16:46:12 18.09.2007 14:46:12 Archive The property names are located on the left and their content on the right. You now know how to find out which properties an object contains. Formatting Pipeline Results Transforming objects produced by the pipeline is carried out by formatting cmdlets. There are four choices: Get-Command -verb format CommandType Name -------------Cmdlet Format-Custom <Object[]>] [-De... Cmdlet Format-List <Object[]>] [-Grou... Cmdlet Format-Table <Object[]>] [-Aut... Cmdlet Format-Wide <Object>] [-AutoSi... Definition ---------Format-Custom [[-Property] Format-List [[-Property] Format-Table [[-Property] Format-Wide [[-Property] These formatting cmdlets are not just useful for converting all of an object's properties into text, but you can also select the properties you want to see. Displaying Particular Properties To accomplish this, you type the property that you want to see and not just an asterisk behind the cmdlet. If you do not want to explicitly use a table or list format, it is considered best practice to use Select-Object rather than Format* because Select-Object will automatically determine the best formatting and will also return objects that can be processed by subsequent cmdlets. When you use Format-* cmdlets, objects are converted into formatting information, which can only be interpreted by Out-* cmdlets which is why Format-* cmdlets must be used only at the end of your pipeline. The next instruction will retrieve you a directory listing with only Name and Length. Because sub-directories don't have a property called Length, you will see that the Length column for the sub-directory is empty: Dir | Select-Object Name, Length Name ---Sources Test 172.16.50.16150.dat 172.16.50.17100.dat output.htm output.txt Length -----16 16 10834 1338 Using Wildcard Characters Wildcard characters are allowed. So, the next command will get you information about your video controller and output all properties that have a resolution keyword: Get-WmiObject Win32_VideoController | Select-Object *resolution* CurrentHorizontalResolution CurrentVerticalResolution -------------------------------------------------1680 1050 Scriptblocks and "Aggregate" Properties Script blocks can be used as columns as they basically act as PowerShell instructions included in brackets that work like synthetic properties to calculate their value. Within a script block, the variable $_ will contain the actual object. The script block could convert the Length property into kilobytes if you'd like to output file sizes in kilobytes rather than bytes: Dir | Select-Object name, { [int]($_.Length/1KB) } Name [int]($_.Length/1KB) ---------------------output.htm 11 output.txt 13 backup.pfx 2 -- cmdlet.txt 23 Or maybe you'd like your directory listing to show how many days have passed since a file or a folder was last modified. By using the New-TimeSpan cmdlet, you can calculate how much time has elapsed up to the current date. To see how this works, you can look at the line below as an example that calculates the time difference between January 1, 2000, and the current date:/p> New-TimeSpan "01/01/2000" Days : 4100 Hours : 21 Minutes : 13 Seconds : 15 Milliseconds : 545 Ticks : 3543163955453834 TotalDays : 4100,8842077012 TotalHours : 98421,2209848287 TotalMinutes : 5905273,25908972 TotalSeconds : 354316395,545383 TotalMilliseconds : 354316395545,383 Use this script block to output how many days have elapsed from the LastWriteTime property up to the current date and to read it out in its own column: {(New-TimeSpan $_.LastWriteTime ).Days} Dir would then return a sub-directory listing that shows how old the file is in days: Dir | Select-Object Name, Length, {(New-TimeSpan $_.LastWriteTime ).Days} Name Length (New-TimeSpan $_.LastWriteTime (Get-Date)).Days --------- ----------------------------------------------Application data 61 Backup 55 Contacts 158 Debug 82 Desktop 19 Documents 1 (...) Changing Column Headings When you use synthetic properties, you will notice that column headings look strange because PowerShell puts code in them that computes the column contents. However, after reading the last chapter, you now know that you can use a hash table to format columns more effectively and that you can also rename them: $column = @{Expression={ [int]($_.Length/1KB) }; Name="KB" } Dir | Select-Object name, $column Name KB ----- output.htm 11 output.txt 13 backup.pfx 2 cmdlet.txt 23 Optimizing Column Width Because the pipeline processes results in real time, PowerShell cannot know how wide of a space the column elements will occupy. As a result, it will tend to be generous in sizing columns. If you specify the -AutoSize parameter, Format-Table will collect all results first before setting the maximum width for all elements. You can optimize output, but the results will no longer be output in real time: $column = @{Expression={ [int]($_.Length/1KB) }; Label="KB" } Dir | Format-Table name, $column -AutoSize Name KB ----output.htm 11 output.txt 13 backup.pfx 2 cmdlet.txt 23 Sorting and Grouping Pipeline Results Using the cmdlets Sort-Object und Group-Object, you can sort and group other command results. In the simplest scenario, you can just append Sort-Object to a pipeline command and your output will already be sorted. It's really very simple: Dir $env:windir | Sort-Object When you do that, Sort-Object will select the property it uses for sorting. It's better to choose the sorting criterion yourself as every object‘s property may be used as a sorting criterion. For example, you could use one to create a descending list of a sub-directory's largest files: Dir $env:windir | Sort-Object -property Length -descending You must know which properties are available to use Sort-Object and all the other following cmdlets. In the last section, you learned how to do that. Send the result of a cmdlet to Select-Object *, and you'll get a list of all properties available that you can use for sorting: Dir $env:windir | Select-Object * Sort-Object can sort by more than one property at the same time. For example, if you'd like to sort all the files in a folder by type first (Extension property) and then by name ( Name property), you can specify both properties: Dir | Sort-Object Extension, Name Sort Object and Hash Tables Sort-Object can use hash tables to better control the sorting. Let's assume that you want to sort a folder by file size and name. While the file size should be sorted in descending order, file names should be sorted in ascending order. You can solve this problem by passing Sort-Object to a hash table (see Chapter 4). Dir | Sort-Object @{expression="Length";Descending=$true},@{expression="Name"; Ascending=$true} The hash table will allow you to append additional information to a property so you can separately specify for each property your preferred sorting sequence. Grouping Information Group-Object works by grouping objects based on one or more properties and then counting the groups. You will only need to specify the property you want to use as your grouping option. The next line will return a status overview of services: Get-Service | Group-Object Status Count Name Group ----- -------91 Running {AeLookupSvc, AgereModemAudio, Appinfo, Ati External Event Utility...} 67 Stopped {ALG, AppMgmt, Automatic LiveUpdate - Scheduler, BthServ...} The number of groups will depend only on how many different values are found in the property specified in the grouping operation. The results' object contains the properties Count, Name, and Group. Services are grouped according to the desired criteria in the Group property. The following will show you how to obtain a list of all currently running services: $result = Get-Service | Group-Object Status $result[0].Group In a file system, Group-Object could group files based on type: Dir $env:windir | Group-Object Extension Dir $env:windir | Group-Object Extension | Sort-Object Count -descending Count Name Group ----- -------22 {Application data, Backup, Contacts, Debug...} 16 .ps1 {filter.ps1, findview.PS1, findview2.PS1, findview3.PS1...} 12 .txt {output.txt, cmdlet.txt, ergebnis.txt, error.txt...} 4 .csv {ergebnis.csv, history.csv, test.csv, test1.csv} 3 .bat {ping.bat, safetycopy.bat, test.bat} 2 .xml {export.xml, now.xml} 2 .htm {output.htm, report.htm} Using Grouping Expressions The criteria used to group objects can also be calculated. The next example uses a script block which returns True if the file size exceeds 100 KB or False otherwise. All files larger than 100KB are in the True group:. Dir | Group-Object {$_.Length -gt 100KB} Count Name ----- ---67 False Debug...} 2 True reported which Group ----{Application data, Backup, Contacts, {export.xml, now.xml} in the column Count is The script block is not limited to returning True or False. The next example will use a script block that returns a file name's first letter. The result: Group-Object will group the sub-directory contents by first letters: Dir | Group-Object {$_.name.SubString(0,1).toUpper()} Count Name Group ----- -------4 A {Application data, alias1, output.htm, output.txt} 2 B {Backup, backup.pfx} 2 C {Contacts, cmdlet.txt} 5 D {Debug, Desktop, Documents, Downloads...} 5 F {Favorites, filter.ps1, findview.PS1, findview2.PS1...} 3 L {Links, layout.lxy, liste.txt} 3 M {MSI, Music, meinskript.ps1} 3 P {Pictures, p1.nrproj, ping.bat} 7 S {Saved Games, Searches, Sources, SyntaxEditor...} 15 T {Test, test.bat, test.csv, test.ps1...} 2 V {Videos, views.PS1} 1 [ {[test]} 1 1 {1} 4 E {result.csv, result.txt, error.txt, export.xml} 4 H {mainscript.ps1, help.txt, help2.txt, history.csv} 1 I {info.txt} 2 N {netto.ps1, now.xml} 3 R {countfunctions.ps1, report.htm, root.cer} 2 U {unsigned.ps1, .ps1} This way, you can even create listings that are divided into sections: Dir | Group-Object {$_.name.SubString(0,1).toUpper()} | ForEach-Object { ($_.Name)*7; "======="; $_.Group} (...) . 30 Microsoft Corporation 1 Adobe Systems.pfx Contacts 23586 cmdlet. you can filter them based on their Status property if you want to list only running services. you can use Where-Object to filter results.BBBBBBB ======= d----a--CCCCCCC ======= d-r--a--DDDDDDD ======= d---d-r-d-r-d-r--a--(. 1 Symantec Corporation 2 ATI Technologies Inc. Inc.2007 13.2007 13.) 26.2007 11:03 16:05 15:05 13:41 18:33 15:56 13:29 11:22 11:43 Backup 1732 backup.2007 26. For example.vbs You can use the parameter -NoElement if you don't need the grouped objects and only want to know which groups exist.txt Debug Desktop Documents Downloads 1046 drive. Inc.2007 28.08.04. This will save a lot of memory: Get-Process | Group-Object -property Company -noelement Count Name ----..07.---50 1 AuthenTec.09. Filtering Pipeline Results If you're only interested in certain objects.2007 24.2007 17.04. Get-Service | Where-Object { $_. 1 BlazeVideo Company 1 ShellTools LLC 2 Infineon Technologies AG 1 Just Great Software 1 Realtek Semiconductor 1 Synaptics.Status -eq "Running" } Status Name DisplayName ------------------Running AeLookupSvc Applicationlookup Running AgereModemAudio Agere Modem Call Progress Audio Running Appinfo Applicationinformation Running AppMgmt Applicationmanagement .2007 30. Inc.09.09. 2 LG Electronics Inc.2007 17.08. 1 BIT LEADER 1 LG Electronics 1 Intel Corporation 2 Apple Inc.06. . It can select the columns (properties) you want to see. Here is another example of what a pipeline filter could look like: Get-WmiObject Win32_Service | Where-Object {($_. the object will be let through.. you may not retrieve the information from some processes.StartMode -eq "Auto")} | Format-Table ExitCode Name ProcessId State Status -------. StartTime Analyzing and Comparing Results .. So Where-Object really works like a condition (see Chapter 7): if the expression results in $true.. # List the five largest files in a directory: Dir | Sort-Object Length -descending | Select-Object -first 5 # List the five longest-running processes: Get-Process | Sort-Object StartTime | Select-Object -last 5 | Select-Object ProcessName.. and it can show only the first or the last results.--------------------0 Automatic Li. However.Running Running Running Running Running (.) Ati External Ev.... The current object that is travelling the pipeline is found in $_... Audiosrv BFE BITS Ati External Event Utility Windows-Audio-Endpoint-building Windows-Audio Basis filter Engine Intelligent Background Transmiss. 0 Stopped OK 0 WinDefend 0 Stopped OK $false) -and StartMode --------Auto Auto Auto Auto Limiting Number of Objects Select-Object has a dual purpose. StartTime If you aren't logged on with administrator privileges. AudioEndpointBu. you can avoid exceptions by adding -ErrorAction SilentlyContinue (shortcut: -ea 0): Get-Process | Sort-Object StartTime -ea 0 | Select-Object -last 5 | Select-Object ProcessName. Where-Object takes a script block and evaluates it for every pipeline object.Started -eq ($_... 0 Stopped OK 0 ehstart 0 Stopped OK 0 LiveUpdate Notic. log | Measure-Object -character -line word Exporting Pipeline Results If you'd like to save results into a text file or print it. Statistical Calculations Using the Measure-Object cmdlet. which you can use to set the output format If you don't remember which encoding formats are allowed. For example.txt -Width 1000 Out-File will support the parameter -Encoding.csv . bigendianunicode. if you want to check file sizes.Using the cmdlets Measure-Object and Compare-Object. you can get statistic information. you can measure and evaluate PowerShell command results. rather than outputting it into the console. to open results in Microsoft Excel. Just specify an invalid value and then the error message will tell you which values are allowed: Dir | Out-File -encoding Dunno Out-File : Cannot validate argument "Dunno" because it does not belong to the set "unicode. Out-* cmdlets turn results into plain text so you are reducing the richness of your results ( Out-GridView is the only exception to the rule which displays the results in an extra window as a mini-spreadsheet). For example. let Dir give you a directory listing and then examine the Length property: Dir $env:windir | Measure-Object Length -average -maximum -minimum -sum Count : 50 Average : 36771.csv Invoke-Item $env:temp\report. utf32. utf7. words. Export it instead and use one of the xport-* cmdlets to preserve the richness of your results. Measure-Object will allow you to determine how often particular object properties are distributed. do this: Get-Process | Export-CSV -UseCulture -NoTypeInformation -Encoding UTF8 $env:temp:\report. and lines in them: Get-Content $env:windir\windowsupdate. append Format-* | Out-* to any PowerShell command: Get-Process | Format-Table -AutoSize | Out-File $env:temp\somefile. oem". For example. utf8. Compare-Object will enable you to compare before-and-after snapshots. default. ascii.76 Sum : 1838588 Maximum : 794050 Minimum : 0 Property : Length Measure-Object also accepts text files and discovers the frequency of characters. you will learn what objects are and how to get your hands on PowerShell objects before they get converted to simple text.09. Converting objects into HTML formats is done by ConvertTo-Html: Get-Process | ConvertTo-Html | Out-File output.Core\FileSystem::C:\Users\Tobias Weltner Mode LastWriteTime Length Name --------------------.hta In this chapter. Topics Covered: Objects = Properties + Methods o Creating a New Object o Adding Properties o Adding Methods Properties: What an Object "Is" o Properties Containing Objects o Read-Only and Read-Write Properties Table 6.hta .\output. Description | ConvertTo-Html -title "Process Report" | Out-File output.---d---19.1: Properties of the RawUI object o Property Types o Listing All Properties Methods: What an Object "Can Do" o Eliminating "Internal" Methods Get_ and Set_ Methods .\output.2007 14:31 testdirectory rm testdirectory # Here the command output is sent to "nothing": md testdirectory | Out-Null rm testdirectory # That matches the following redirection: md testdirectory > $null rm testdirectory HTML Outputs If you‘d like.Suppressing Results You can send the output to Out-Null if you want to suppress command output: # This command not only creates a new directory but also returns the new directory: md testdirectory Directory: Microsoft.hta Get-Process | Select-Object Name.hta .PowerShell. PowerShell can also pack its results into (rudimentary) HTML files. 3: Different property types Using Object Methods Different Method Types Table 6. The object is red.NET Types Converting Object Types Using Static Type Members Using Dynamic Object Instance Members Creating New Objects o Creating New Objects with New-Object Using Constructors o New Objects by Conversion o Loading Additional Assemblies: Improved Internet Download o Using COM Objects Which COM Objects Are Available? How Do You Use COM Objects? Summary Objects = Properties + Methods In real life. Objects in PowerShell are similar. Using New-Object. Properties and methods are called members. even a virtual pocketknife. such as its color. an object is very similar: its nature is described by properties. like a pocketknife. In the computing world. has three blades. Creating a New Object Let's turn our real-life pocketknife into a virtual pocketknife.4: Different types of methods Using Static Methods o Table 6. over the telephone? You would probably carefully examine the object and then describe what it is and what it can do: Properties: A pocketknife has particular properties. such as cut. you already know what an object is: everything you can touch. Methods: n addition. So properties< describe what an object is. and remove corks. How would you describe this object to someone. into a PowerShell object.NET object o Calling a Method o Call Methods with Arguments Which Arguments are Required? o Several Method "Signatures" Playing with PromptForChoice Working with Real-Life Objects o Storing Results in Variables Using Object Properties PowerShell-Specific Properties Table 6. and is made by the firm Idera.2: Standard methods of a . you can do things with this object. The object can cut. Let's turn a typical real-world object. PowerShell can generate new objects. you will need a new and empty object: . weights 55 grams. screw. or number of blades. and the actions it can perform are called its methods. First.5: Mathematical functions from the [Math] library o Finding Interesting . manufacturer. Standard Methods Table 6. turn screws. size. Everything that an object can is called its methods. or pull corks out of wine bottles. and then the property name: # Display a particular property: $pocketknife | Select-Object -expandProperty Manufacturer $pocketknife. you can add properties to the object. you've described the object in $pocketknife with a total of four properties. To do that. You can access the value of a specific property by either using Select-Object with the parameter -expandProperty. we use positional parameters to shorten the code necessary to add members to the object: $pocketknife | Add-Member NoteProperty Weight 55 $pocketknife | Add-Member NoteProperty Manufacturer Idera $pocketknife | Add-Member NoteProperty Blades 3 By now. This time. Here. # Adding a new property: Add-Member -MemberType NoteProperty -Name Color-Value Red -InputObject $pocketknife You can uUse the Add-Member cmdlet to add properties. PowerShell will automatically convert the object into readable text: # Show all properties of the object all at once: $pocketknife Color Weight Blades ----------------Red 55 3 Manufacturer ---------Idera You will now get a quick overview of its properties when you output the object to the console. PowerShell will return "nothing": $pocketknife Adding Properties Next.$pocketknife = New-Object Object This new object is actually pretty useless. If you call for it. it suddenly has a property telling the world that its color is red: $pocketknife Color ----Red You can then add more properties to describe the object even better. If you call for the object now. If you output the object in $pocketknife in the PowerShell console. let's start describing what our object is.manufacturer . you added the property color with the value red to the object $pocketknife. or add a dot. Properties only describe what an object is. For example. methods perform actions. you used the Add-Member cmdlet. not methods: $pocketknife Color Blades ----------Red 3 Weight Manufacturer ------. a ScriptMethod). something interesting like this will happen: # If you don't use parentheses.corkscrew Script : "Pop! Cheers!" OverloadDefinitions : {System.Object corkscrew(). The actions your object can do are called its methods. not what it can do. so be sure to not put a space between the method name and the opening parenthesis. So.Adding Methods With every new property you added to your object. Name : corkscrew IsInstance : True . but it still really can't do anything.} MemberType : ScriptMethod TypeNameOfValue : System.it's in!" } $pocketknife | Add-Member ScriptMethod corkscrew { "Pop! Cheers!" } Again. Parentheses formally distinguishes properties from methods. The value is a scriptblock marked by brackets. you'll retrieve information on a method: $pocketknife. while properties merely provide information. Always remember to add parentheses to method names.corkscrew() Pop! Cheers! Your object really does carry out the exact script commands you assigned to the corkscrew() method. but this time you added a method instead of a property (in this case. which contains the PowerShell instructions you want the method to perform.. you can use this code: $pocketknife. $pocketknife has been gradually taking shape. If you output your object.Object Value : System.Object corkscrew().---------55 Idera You can add a dot and then the method name followed by two parentheses to use any of the three newly added methods. If you forget them. if you'd like to remove a cork with your virtual pocketknife.. it will still look the same because PowerShell only visualizes object properties. They are part of the method name. So let's teach your object a few useful methods: # Adding new methods: $pocketknife | Add-Member ScriptMethod cut { "I'm whittling now" } $pocketknife | Add-Member ScriptMethod screw { "Phew. the version in itself is again a special object designed to store version numbers.0 InstanceId : e32debaf-3d10-4c4c-9bc6-ea58f8f17a8f UI : System. So.0. the OverloadDefinitions information is in an additional object. Properties: What an Object "Is" There are just two important rules: Properties describe an object. if you want to find out which PowerShell version you're using. you could access and return the Version property: $Host.Automation.0. Build. The version isn't displayed as a single number. Let's check out the data type that the Version property uses: $version = $Host. Check out the properties in $host! $Host Name : ConsoleHost Version : 1.Host. you know these are object properties that PowerShell has just converted into text. PowerShell displays four columns: Major.Object corkscrew().FullName System. And object properties are automatically turned into text when you output the object to the console. Whenever you see columns. For PowerShell.Version $version. The "virtual pocketknife" example reveals that objects are containers that contain data (properties) and actions (methods).Internal. Next. So.PowerShell.Management. let's take a look at a more interesting object: PowerShell! There is a variable called $host which represents your PowerShell host.GetType().corkscrew $info. absolutely everything is an object so you can store the object in a variable and then specifically ask the OverloadDefinitions property for information: # Information about a method is returned in an object of its own: $info = $pocketknife.ConsoleHost+ConsoleColorProxy The object stored in the variable $host apparently contains seven properties.----1 0 0 Revision -------0 It works—you get back the PowerShell host version. The properties‘ names are listed in the first column. As you'll see later. In fact.Version Major Minor Build ----. Instead. Our virtual pocketknife was a somewhat artificial object with no real use.----. Minor.InternalHostUserInterface CurrentCulture : en-US CurrentUICulture : en-US PrivateData : Microsoft.OverloadDefinitions System. and Revision.You just received a method description. it reveals the exact way to use a command for any method.Version . What's interesting about this is mainly the OverloadDefinitions property. That's enough to investigate any object. DisplayName.The version is not stored as a String object but as a System.-------12 55 3 28334 The CurrentCulture property is just another example of the same concept.CultureInfo]'de-DE' LCID Name DisplayName ----------------1031 de-DE German (Germany) You can also convert the LCID into a CultureInfo object by converting a suitable number: [System. Name. You can convert things by adding the target type in square brackets in front of the string: [System.CurrentCulture.Version. then make PowerShell convert the string into a System. This object type is perfect for storing versions.Version.CurrentCulture.Version type. If you want to know which international version of PowerShell you are using.55.CultureInfo]1033 .CurrentCulture.Version. you can read the DisplayName property: $Host.Major 1 $Host.CultureInfo Country properties are again stored in a highly specialized type that describes a culture with the properties LCID. you can convert any suitable string into a CultureInfo-object. and DisplayName.28334' Major Minor Build Revision ----.Globalization. you can use it for your own purposes as well.String Likewise.FullName System.GetType().Version]'12.----. Read this property to find out its type: $Host.Globalization. simply make sure the string consists of four numbers separated by dots (the typical format for versions).3.Build 0 Knowing an object type is very useful because once you know there is a type called System.DisplayName English (United States) $Host. allowing you to easily read all details about any given version: $Host.Version object. Try this if you wanted to find out details about the 'de-DE' locale: [System.----.Globalization.FullName System.CurrentCulture LCID Name ------1033 en-US DisplayName ----------English (United States) $Host. Try to convert a simple string of your choice into a rich version object! To do that.GetType(). Internal. You can read all of these properties. When you output $host into the console.50 120.Two properties in $host seem to be special: UI and PrivateData. this data is stored in various other objects.0. all other properties will be converted into readable text – except for the properties UI and PrivateData: $Host Name : ConsoleHost Version : 1.InternalHostRawUserInterface You see that the property UI contains only a single property called RawUI.Management.Host.rawui ForegroundColor BackgroundColor CursorPosition WindowPosition CursorSize BufferSize WindowSize MaxWindowSize MaxPhysicalWindowSize KeyAvailable WindowTitle : : : : : : : : : : : DarkYellow DarkMagenta 0. in which yet another object is stored.0 InstanceId : e32debaf-3d10-4c4c-9bc6-ea58f8f17a8f UI : System.ui. In turn.UI RawUI ----System. Let's see what sort of object is stored in the RawUI property: $Host.Automation.0.PowerShell.62 140.3000 120.Host.Automation. but can you also change them? Read-Only and Read-Write Properties .87 25 120.Internal.LCID ---1033 Name ---en-US DisplayName ----------English (United States) Properties Containing Objects The properties of an object store data. If you'd like to find out what is actually stored in the UI property.ConsoleHost+ConsoleColorProxy This is because both these properties again contain an object.136 0. you can read the property: $Host.Management.62 False PowerShell "RawUI" stands for "Raw User Interface" and exposes the raw user interface settings your PowerShell console uses.InternalHostUserInterface CurrentCulture : en-US CurrentUICulture : en-US PrivateData : Microsoft. Property Description Text color. the size of a blinking cursor is specified as a number from 0 to 100 and corresponds to the fill percentage. Other properties cannot be changed. DarkCyan. You cannot control that by changing a property.rawui. Red. So. Blue.rawui. if you modify a property. DarkGray.keyavailable = $true "KeyAvailable" is a ReadOnly-property. Green. Background color. DarkMagenta. DarkBlue. Yellow.ui. and White. $Host. DarkGreen. ForegroundColor DarkMagenta.1: Properties of the RawUI object Property Types Some properties accept numeric values. and White. Optional values are Black. you'll get an error message: $Host. the underlying object has to also be modified to reflect that change. the console will change colors accordingly. Yellow. At line:1 char:16 + $Host. For example. so this property refuses to be changed.ui. Gray. Gray. Red. Optional values are Black. Your property changes are reflected by the object.ui. the property cannot be changed and is called "read-only. Values outside the 0-100 numeric range will generate an error: .ForegroundColor = "White" Type cls so the entire console adopts this color scheme. Cyan. Blue. Magenta. If you do. what happens next? Properties need to accurately describe an object." Console background and foreground colors are a great example of properties you can easily change. If this is not possible.BackgroundColor = "Green" $Host. Cyan. DarkBlue. DarkGray.k <<<< eyavailable = $true Whether the console receives key press input or not. DarkYellow. Magenta.rawui. DarkGreen. DarkCyan. You can only read it. Green. too? And if you can. BackgroundColor DarkRed.Can you actually change properties. DarkRed. DarkYellow. depends on whether you pressed a key or not. If you try anyway. and the changed properties still accurately describe the object. CursorPosition Current position of the cursor WindowPosition Current position of the window CursorSize Size of the cursor BufferSize Size of the screen buffer WindowSize Size of the visible window MaxWindowSize Maximally permissible window size MaxPhysicalWindowSize Maximum possible window size KeyAvailable Makes key press input available WindowTitle Text in the window title bar Table 6.ui. The next line sets a cursor size of 75%.rawui. ui.Management.} CursorSize Property System. you get no hint.set.rawui.Automation. DarkRed.---------BackgroundColor Property System. Some object types are more specific than others.ForegroundColor = "pink" Exception setting "ForegroundColor": "Cannot convert value "pink" to type "System. you will receive an error message listing the colors you can use: # Colors are specified as text (in quotation marks): $Host.RawUI | Get-Member -MemberType Property TypeName: System.set. Why? Every property expects a certain object type. DarkCyan.Host.rawui. Yellow. Green." At line:1 char:16 + $Host.rawui.rawui. Instead. you cannot specify any color that comes to mind. DarkGray.Management. Parameter name: value Actual value was 1000.} CursorPosition Property System. Blue.Size BufferSize {get. DarkBlue.ForegroundColor = "yellow" # Not all colors are allowed: $Host.ui. DarkGreen. Red. Cyan. DarkYellow.cursorsize = 1000 Exception setting "CursorSize": "Cannot process "CursorSize" because the cursor size specified is invalid. Specify one of the following enumeration values and try again.Management. the error message will list the possible values. White".Internal.} BufferSize Property System.ConsoleColor BackgroundColor {get.InternalHostRawUserInterface Name MemberType Definition ------------.Automation.Host.ui. DarkMagenta.F <<<< oregroundColor = "pink" If you assign an invalid value to the property ForegroundColor.ui.Host. However.Coordinates CursorPosition {get. If you assign an invalid value to the property CursorSize.set.} ." At line:1 char:16 + $Host. PowerShell expects a "valid" color and if your color is unknown. Magenta.Automation.ui.set.cursorsize = 75 # Values outside this range will generate an error: $Host.ConsoleColor" due to invalid enumeration values.rawui.ui. Gray. The possible enumeration values are "Black.rawui.c <<<< ursorsize = 1000 Other properties expect color settings.Int32 CursorSize {get. You can use GetMember to find out which object types a given property will expect: $Host.# A value from 0 to 100 is permitted: $Host.ui. Management. you are actually not violating the object boundaries because the value of 1.Automation.Automation.Host.Int32 object.Host.Automation. So.Automation. you can use the WindowSize property.Coordinates WindowPosition {get.Host. As it turns out.Boolean KeyAvailable {get. a so-called enumeration: [system.ConsoleColor]) Black DarkBlue DarkGreen DarkCyan DarkRed DarkMagenta DarkYellow Gray DarkGray Blue Green Cyan Red Magenta Yellow White If you do not specify anything contained in the enumeration.Management.Size.Management.} MaxWindowSize Property System.set.NET method called GetNames() to list the possible values defined in that enumeration: [System. the error message will simply return the enumeration‘s contents. the property expects a new window size wrapped in an object of type System. So. In the case of CursorSize. you will receive only an indication that your value is invalid.000 can be stored in a System.ConsoleColor type. For example.Int32 object.} As you can see. which is simply a 32-bit number.set.Host.ConsoleColor ForegroundColor {get. You get an error message anyway because of the validation code that the CursorSize property executes internally.ConsoleColor]. whether you get detailed error information will really depend on the property‘s definition. ForegroundColor expects a System.Automation. CursorSize stores its data in a System. if you'd like to change the PowerShell window size.set.Management. a property expects a value to be wrapped in a specific object.} WindowPosition Property System. if you try to set the cursor size to 1.ForegroundColor Property System.Size MaxWindowSize {get.} WindowTitle Property System.} MaxPhysicalWindowSize Property System.IsEnum True Whenever a type is an enumeration. Where can you get an object like that? .Size MaxPhysicalWindowSize {get. but not why.} KeyAvailable Property System. Sometimes.Size WindowSize {get.Management. you can use a special .000. This type is a highly specialized type: a list of possible values.String WindowTitle {get.set.} WindowSize Property System.Enum]::GetNames([System.Host. WindowSize $value Width Height ---------110 64 $value.} .100 Exception setting "WindowSize": "Cannot convert "System.Host.} PrivateData Property System.Globalization.Object[]" to "System.30) Listing All Properties Get-Member will return detailed information about them because properties and methods are all members of an object.CultureInfo CurrentCulture {get. you can use the memberType parameter and specify "property": $Host | Get-Member -memberType property Name MemberType Definition ------------.Management.$Host.ui.Automation.WindowSize = New-Object System.ui.rawui.Host." At line:1 char:16 + $Host.ui.Automation.ui.Automation.Management.} InstanceId Property System. change the result. you can freshly create the object you need by using New-Object: $value = New-Object System.Size(80.Globalization. and then write back the changes. To limit Get-Member to only properties.rawui.30) $Host.rawui.WindowSize = $value Or in a line: $host.rawui.100 There are a number of ways to provide specialized objects for properties. here's how you would change the PowerShell window size to 80 x 30 characters: $value = $Host.PSHostUserInterface UI {get.Automation.WindowSize = 100.PSObject PrivateData {get.Automation. The easiest approach: read the existing value of a property (which will get you the object type you need).Width = 80 $value.ui.} UI Property System.Guid InstanceId {get.} CurrentUICulture Property System.ui.Host.WindowSize = $value Or. For example.Management.Size".CultureInfo CurrentUICulture {get.Height = 30 $Host.rawui.rawui.Management.Host. Let's use Get-Member to examine all properties defined in $host.W <<<< indowSize = 100.Size(80.---------CurrentCulture Property System.} Name Property System.Management.String Name {get. curly brackets will report whether the property is read-only ({get.} MaxWindowSize Property System.} CursorSize Property System. The wildcard in front of "property" will also select all specialized properties like "ScriptProperty.Host.} WindowTitle Property System. There are different "sorts" of properties.Size MaxWindowSize {get.} CursorPosition Property System. The Version property uses the System.} WindowSize Property System.String type.Version Version {get.} MaxPhysicalWindowSize Property System.Management.Host.set. take a look at the $host.Automation.Size WindowSize {get.ConsoleColor ForegroundColor {get.Host.set.Automation.} BufferSize Property System.Version Property System.} WindowPosition Property System.ConsoleColor BackgroundColor {get.ui. you can use the -MemberType parameter and assign it a value of *Property.Version type.Host.rawui object: $Host.set.InternalHost Name ---EnterNestedPrompt MemberType Definition ---------. you will now see all supported properties in $host." Methods: What an Object "Can Do" Methods are things that an object can do.Management. while others could not. So if you really want to list all properties.Management.} KeyAvailable Property System.Host.set.Size MaxPhysicalWindowSize {get.ui.} In the column Name.Host.Coordinates WindowPosition {get.Automation.set.Automation.} This result is more differentiated. At the end of each definition.Management.}).Coordinates CursorPosition {get.---------Method System. Now. In the column Definition.set.Boolean KeyAvailable {get. It shows you that some properties could be changed.Only its properties are converted into readable text when you output an object to the console.Size BufferSize {get. Methods remain invisible.Int32 CursorSize {get. You can use Get-Member and the parameter "memberType" with the value "method" to list the methods of an object: $Host | Get-Member -memberType Method TypeName: System.}) or can also be modified ({get. the property object type is listed first.rawui | Get-Member -membertype property BackgroundColor Property System.} ForegroundColor Property System. For example.set. Most properties are of the Property type. you can see that the Name property stores a text as System.Automation.Management.set.set.Automation. but PowerShell can add additional properties like ScriptProperty.Automation. You can see at a glance that all properties of the $host object are only readable.Internal.Void EnterNestedPrompt() .String WindowTitle {get.Management.Management.Host. version Major ----2 Minor ----0 Build -----1 Revision --------1 # Query property value using getter method: $Host.) get_CurrentCulture Method System.. It will skip methods that are used internally.Automation.Void NotifyBeginApplication() System.Globalization..get_Version() Major Minor Build Revision . You can force Get-Member to list all methods by adding the -Force parameter: PS > $Host | Get-Member -memberType Method -Force TypeName: System.Void SetShouldExit(int exitCode) string ToString() Eliminating "Internal" Methods Get-Member does not list all methods defined by an object.---------(.Globalization.Management..Object obj) System.Internal.Host..Guid get_InstanceId() get_IsRunspacePushed Method bool get_IsRunspacePushed() get_Name Method string get_Name() get_PrivateData Method psobject get_PrivateData() get_Runspace Method runspace get_Runspace() get_UI Method System....Void NotifyEndApplication() System.Version get_Version() (. get_CurrentUICulture Method System.Host.Management.Void ExitNestedPrompt() int GetHashCode() type GetType() System.PSHostUs. # Query property: $Host.CultureInfo get_Curre.Equals ExitNestedPrompt GetHashCode GetType NotifyBeginApplication NotifyEndApplication PopRunspace PushRunspace SetShouldExit ToString Method Method Method Method Method Method Method Method Method Method bool Equals(System.Void PopRunspace() System. get_InstanceId Method System. So the method "get_someInfo()" will retrieve the very same information you could also have gotten with the "someInfo" property.) Get_ and Set_ Methods Any method that starts with "get_" is really designed to retrieve a property value....InternalHost Name MemberType Definition ------------. get_Version Method System.CultureInfo get_Curre.Automation.Void PushRunspace(runspace runspace) System. Standard Methods In addition.Type GetType() . They can temporarily stop a function or script so you can verify variable contents or make code changes. Note in this example: all properties of the $host object can only be read so there are no Set_ methods.NET object Calling a Method Before you invoke a method: make sure you know what the method will do. so be sure to exit it again using the exit command or call $host. which could be dangerous. Call Methods with Arguments There are many useful methods in the UI object.InternalHostUserInterface Name ---Equals GetHashCode GetType MemberType ---------Method Method Method Definition ---------System.Int32 GetHashCode() System. such as Add_ and Remove_ methods. You'll learn more about this in Chapter 11. when a method name contains an underscore.Host. Nested prompts are not especially useful in a normal console.Management.ExitNestedPrompt(). nearly every object contains a number of "inherited" methods that are also not specific to the object but perform general tasks for every object: Method Equals GetHashCode GetType ToString Description Verifies whether the object is identical to a comparison object Retrieves an object's digital "fingerprint" Retrieves the underlying object type Converts the object into readable text Table 6.2: Standard methods of a . after which you continue the code by entering exit.Automation.----2 ----0 -----1 --------1 The same is true for Set_ methods: they change a property value and exist for properties that are read/writeable. Add an opened and closed parenthesis. Generally speaking. There can be more internal methods like this. Methods are commands that do something.Boolean Equals(Object obj) System.Internal. it is most likely an internal method.ui | Get-Member -membertype Method TypeName: System. You have used EnterNestedPrompt() to open a nested prompt. Here's how you get a good overview: $Host. Nested prompts can be useful in functions or scripts because they work like breakpoints. like this: $host. You can add a dot to the object and then the method name to call a method.EnterNestedPrompt() The PowerShell prompt changes to ">>" (unless you changed your default prompt function). String ReadLine() ReadLineAsSecureString Method System.Host. WriteDebugLine Method System. it is System.Automation. a special object type because it represents "nothing": the method doesn't return anything at all. WriteProgress Method System.Management..Management.Security. System.PSCredential PromptForCredential(String cap.Void WriteWarningLine(String message) Most methods require additional arguments from you. System.Void WriteProgress(Int64 sourceId..ui: $info = $Host.Automation.Collections..Automation.Void WriteDebugLine(String message) # Definition shows which arguments are required and which result will be returned: $info. Which Arguments are Required? Pick out a method from the list.UI | Get-Member WriteDebugLine # $info contains all the data on this method: $info TypeName: System. Here is how you call WriteDebugLine(): .Void WriteLine().... PromptForChoice Method System. Every definition will begin with the object type that a method returns. a method‘s name follows. PromptForCredential Method System.InternalHostUserInterface Name MemberType Definition ------------. not a function.Int32 PromptForChoice(String caption.get_RawUI Method System.Internal..Void Write(String value). which is then followed by required arguments..Void WriteLine(String value).String ToString() Write Method System. Versio.Void. ReadLine Method System.Void Write(ConsoleColor foregrou.. WriteDebugLine needs exactly one argument called message.---------WriteDebugLine Method System.Management. Let's pick WriteDebugLine(): # Ask for data on the WriteDebugLine method in $host.Void WriteVerboseLine(String message) WriteWarningLine Method System.String.Generic. In this example. and then ask Get-Member to get you more info.Void WriteDebugLine(String message) WriteErrorLine Method System. A method "returning" System. System.Dictionary`2[[System. ProgressRecord record) WriteVerboseLine Method System. String message. which are listed in the Definition column. Next.PSHostRawUserInterface get_RawUI() Prompt Method System.Host. Collection`.Void WriteDebugLine(String message) The Definition property tells you how to call the method. which is of String type. mscorlib.Voi.Definition System.Void is really a procedure.Void WriteErrorLine(String value) WriteLine Method System..SecureString ReadLineAsSecureString() ToString Method System. "White".Void WriteLine(String value) System.WriteDebugLine("Hello!") Hello! Several Method "Signatures" Some methods accept different argument types. To find out which "signatures" a method supports. To output text.Void WriteLine(ConsoleColor foregroundColor. $info. String value) The definition is hard to read at first.ui.Void WriteLine(String value). ConsoleColor backgroundColor. you can specify one argument only.WriteLine("Red".ui. or even different numbers of arguments.Definition.Replace(").Void WriteLine(ConsoleColor foregroundColor. System.Void WriteLine() System. It introduces special characters.WriteLine() The result is an empty line.Definition System. You can make it more readable by using Replace() to add line breaks. ConsoleColor backgroundColor.$Host.ui. the text itself: $Host.ui. "`n" stands for a line break.WriteLine("Hello world!") Hello world! The third variant adds support for foreground and background colors: $host.UI | Get-Member WriteLine $info. System. Remember the "backtick" character ("`"). String value) This definition tells you: You do not necessarily need to supply arguments: $host.Void WriteLine(). ")`n") System. you can use Get-Member again and look at the Definition property: $info = $Host. ". "Alarm!") WriteLine() actually is the low-level function of the Write-Host cmdlet: Write-Host Write-Host "Hello World!" Write-Host -ForegroundColor Red -BackgroundColor White Alarm! Playing with PromptForChoice . ui. Int32 defaultChoice) Name : PromptForChoice IsInstance : True The definition reveals that this method returns a numeric value ( System.Automation.PSMethod Value : System. 'May the system now be rebooted?'. String message. This is how you can use PromptForChoice() to create a simple menu: $yes = ([System.Management. most methods you examined have turned out to be low-level commands for cmdlets.ChoiceDescription]"&yes") $no = ([System.Host.Host. String message.Management. String message.Int32). It requires a heading and a message respectively as text (String).ChoiceDescription]"&no") $selection = [System. the standard selection.PromptForChoice('Reboot'. You may have noticed by now the limitations of PowerShell's built-in description.UI | Get-Member PromptForChoice $info.PromptForChoice MemberType : Method OverloadDefinitions : {System.Management.So far. Let's first examine which arguments this method expects: $info = $Host.ui.Management. The third argument is a bit strange: Collection`1 choices.ChoiceDescription[]]($yes.1) $selection[$answer] if ($selection -eq 0) { "Reboot" } else { "OK.Int32 PromptForChoice(String caption. Collection`1 choices. The fourth argument is a number (Int32). Collection`1 choices. Int32 defaultChoice) You can get the same information if you call the method without parentheses: You can get the same information if you call the method without parentheses: $Host.Int32 PromptForChoice(String caption. Int 32 defaultChoice)} TypeNameOfValue : System.Automation. then not" } Working with Real-Life Objects .Automation. A new functionality is exposed by the method PromptForChoice().Automation.$selection.Host.$no) $answer = $Host.Int32 PromptForChoice(String caption.Definition System. Collection`1 choices. This is also true for the following methods: Write() (corresponds to Write-Host -nonewline) or ReadLine()/ReadLineAsSecureString() (readhost -asSecureString) or PromptForCredential() (get-credential). much like if you had output the information to the console in the first place: $listing Directory: Microsoft.2007 11:37 Application data d---26.2007 11:03 Backup d-r-13. Storing Results in Variables Save the result to variable to examine the object nature of results you receive from cmdlets.2007 15:05 Contacts d---28.exe # Address a folder: $object = Get-Item $env:windir Using Object Properties You can use Get-Member again to produce a list of all available properties: .) To get to the real objects.PowerShell. so it represents a directory. Dir has stored its result in $listing. $listing = Dir $env:windir When you dump the variable content to the console. It is wrapped in an array since the listing consists of more than one entry..07. the results stored inside of it will be converted to plain text.Every PowerShell command will return objects.Core\FileSystem::C:\Users\Tobias Weltner Mode LastWriteTime Length Name --------------------.---d---20.2007 11:37 Application data The object picked here happens to match the folder Application Data. However..07. Access an array element to get your hands on a real object: # Access first element in listing $object = $listing[0] # Object is converted into text when you output it in the console $object Directory: Microsoft.Core\FileSystem::C:\Users\Tobias Weltner Mode LastWriteTime Length Name --------------------. You can do this if you prefer to directly pick a particular directory or file: # Address a particular file: $object = Get-Item $env:windir\explorer.PowerShell.06.---d---20.04.2007 18:33 Debug (. it is not that easy to get your hands on objects because PowerShell converts them to text whenever you output them to the console.07. you can directly access them inside of a variable. July 20.C.IO.set. 2007 15:31:41 PowerShell-Specific Properties PowerShell can add additional properties to an object.DateTime LastAccessTimeUtc {get.String PSParentPath=Microsoft.FileAttributes Attributes {get.Management.} LastAccessTime Property System.DateTime CreationTimeUtc {get.} LastAccessTimeUtc Property System.} Parent Property System.DirectoryInfo Root {get. Native properties are just called "Property.} Extension Property System.Automation. PSProvider NoteProperty System.} LastWriteTimeUtc Property System.String FullName {get.PowerShell. PSPath NoteProperty System.set.String Name {get.Boolean Exists {get.} Exists Property System. such as "ScriptProperty" or "NoteProperty.String PSPath=Microsoft.} in the column Definition are readable and writeable.DateTime LastAccessTime {get.Name.PowerS. You can actually change their value.set.set.} LastWriteTime Property System.DateTime LastWriteTime {get.IO.} FullName Property System.LastAccessTime Friday. October 1.Boolean PSIsContainer=True PSParentPath NoteProperty System...} Name Property System.set.IO. list all object properties: $object | Get-Member -membertype *property Name MemberType Definition ---------------------Mode CodeProperty System.LastAccessTime = Get-Date # Change was accepted: $object.. Attributes Property System. too.# $object is a fully functional object that describes the "Application Data" directory # First..String PSChildName=Windows PSDrive NoteProperty System.. Whenever that occurs.} Root Property System. 2007 11:37:39 # Change Date: $object.PSDriveInfo PS. Get-Member will label the property accordingly in the MemberType column.String Extension {get.set. by simply assigning a new value (provided you have sufficient privileges): # Determine last access date: $object.set." Properties that are added by PowerShell use a prefix." .set.ProviderInfo P.} BaseName ScriptProperty System.DateTime CreationTime {get.LastAccessTime Monday.} CreationTime Property System.Automation.DirectoryInfo Parent {get..} CreationTimeUtc Property System.Management.String Mode{get=Mode.} Properties marked with {get.DateTime LastWriteTimeUtc {get.} PSChildName NoteProperty System... PSIsContainer NoteProperty System.Object BaseName {get=$this. System.DirectoryInfo CreateSubDirectory(String path).IO.Object GetLifetimeService() GetObjectData Method System.DateTime get_CreationTimeUtc() get_Exists Method System.Void Create()..DirectoryInfo[] GetDirectories().FileAttributes get_Attributes() get_CreationTime Method System.Type GetType() get_Attributes Method System.Void GetObjectData(SerializationInfo info...Void Delete(Boolean recursive) Equals Method System. GetDirectories Method System.---------Create Method System.Remoting.DirectoryInfo[]. System....Int32 GetHashCode() GetLifetimeService Method System.DirectoryInfo Name MemberType Definition ------------. StreamingContext co..IO. GetType Method System.String get_FullName() get_LastAccessTime Method System..ObjRef CreateObjRef(Type requestedType) CreateSubDirectory Method System.Boolean Equals(Object obj) GetAccessControl Method System..Void Delete(). GetFileSystemInfos Method System..Runtime.IO..IO.AccessControl. System. GetHashCode Method System.FileSystemInfo[] GetFileSystemInfos(String searchPattern). System.IO.NET method returns property contents Property Genuine property NoteProperty Subsequently added property with set data value ScriptProperty Subsequently added property whose value is calculated by a script ParameterizedProperty Property requiring additional arguments Table 6. System.IO.Void Create(DirectorySecurity DirectoryS.Security.3: Different property types Using Object Methods Use Get-Member to find out the methods that an object supports: # List all methods of the object: $object | Get-Member -membertype *method TypeName: System.FileIn.IO. S.IO.DateTime get_CreationTime() get_CreationTimeUtc Method System.DirectorySecurity GetAccessControl().String get_Extension() get_FullName Method System.A NoteProperty like PSChildName contains static data..Boolean get_Exists() get_Extension Method System. GetFiles Method System. CreateObjRef Method System. MemberType Description AliasProperty Alternative name for a property that already exists CodeProperty Static .FileInfo[] GetFiles(String searchPattern).Di. PowerShell will add it to tag additional information to an object. . Delete Method System.DateTime get_LastAccessTime() ... A ScriptProperty like Mode executes PowerShell script code that calculates the property‘s value.IO. String get_Name() get_Parent Method System.Void set_CreationTimeUtc(DateTime value) set_LastAccessTime Method System. Try using the first to create a sub-directory and the second to add access permissions.Object InitializeLifetimeService() MoveTo Method System.IO.IO.String ToString() You can apply methods just like you did in the previous examples.Void Refresh() SetAccessControl Method System.get_LastAccessTimeUtc Method System.Void MoveTo(String destDirName) Refresh Method System.DirectoryInfo get_Root() InitializeLifetimeService Method System.Void set_LastAccessTime(DateTime value) set_LastAccessTimeUtc Method System.DateTime get_LastAccessTimeUtc() get_LastWriteTime Method System.Void set_LastAccessTimeUtc(DateTime value) set_LastWriteTime Method System. you can use the CreateSubDirectory method if you'd like to create a new sub-directory.DirectoryInfo CreateSubDirectory(String path.10.Void set_CreationTime(DateTime value) set_CreationTimeUtc Method System.Void set_Attributes(FileAttributes value) set_CreationTime Method System. The next line creates a sub-directory called "My New Directory" without any special access privileges: $object. the pipeline will convert it into text and output it.---d---01. you should find out which arguments this method requires and what it returns: $info = $object | Get-Member CreateSubDirectory $info. ".Void SetAccessControl(DirectorySecurity DirectorySecurity) set_Attributes Method System.DateTime get_LastWriteTime() get_LastWriteTimeUtc Method System. You could just as well have stored the result of the method in a variable: .IO.Void set_LastWriteTime(DateTime value) set_LastWriteTimeUtc Method System.DirectoryInfo get_Parent() get_Root Method System.IO.Definition.DateTime get_LastWriteTimeUtc() get_Name Method System.DirectoryInfo CreateSubDirectory(String path) System. First. For example.2007 15:49 My New Directory Because the method returns a DirectoryInfo object as a result and you haven't caught and stored this object in a variable. DirectorySecurity DirectorySecurity) You can see that the method has two signatures. ")`n") System.Replace(").CreateSubDirectory("My New Directory") Mode LastWriteTime Length Name --------------------.Void set_LastWriteTimeUtc(DateTime value) ToString Method System. static System.DateTime.4: Different types of methods Using Static Methods By now.CreateSubDirectory("Another subdirectory") $subdirectory. You also know by now that each .NET object has a GetType() method with a Fullname property.CreationTime = "September 1. MemberType CodeMethod Method ScriptMethod Description Method mapped to a static .DateTime get_Now() static System.CreationTime Monday. 1980" $subdirectory.DateTime Every type can have its own set of private members called "static" members.GetType(). for example.DateTime get_Today() . pipe it to Get-Member. and objects always have a type. static Sys. September 1. is stored in an object of type System.$subdirectory = $object.. static System.DateTime FromBinary(Int64 static System.Int32 Compare(DateTime t1. FromBinary Method dateData) FromFileTime Method fileTime) FromFileTimeUtc Method fileTime) FromOADate Method get_Now Method get_Today Method Definition ---------static System.Boolean Equals(DateTime t1. which tells you the name of the type this object was derived from: $date = Get-Date $date.DateTime FromOADate(Double d) static System.Int32 DaysInMonth(Int32 year. 1980 00:00:00 Different Method Types Similarly to properties. You can simply specify a type in square brackets. and then use the -static parameter to see the static members of a type.FullName System. static System..String and that a date. PowerShell can also add additional methods to an object.NET method Genuine method Method invokes PowerShell code Table 6. you know that PowerShell stores information in objects. [System.DateTime FromFileTimeUtc(Int64 static System.DateTime Name MemberType ------------Compare Method DateTime t2) DaysInMonth Method Int32 month) Equals Method DateTime t2).DateTime] | Get-Member -static -memberType *method TypeName: System. You know that simple text is stored in objects of type System.DateTime FromFileTime(Int64 static System. $x++) { if( [System. IFormat.. TimeSpan t) op_Equality Method static System.Boolean op_Equality(DateTime d1." These are methods that are called internally whenever you use this data type with an operator.Boolean IsLeapYear(Int32 year) op_Addition Method static System. DateTime t2) op_GreaterThanOrEqual Method static System. String format. $x -lt 2010. TryParseExact Method static System.DateTime ParseExact(String s.DateTime Par. DateTime& result). op_GreaterThanOrEqual is the method that does the internal work when you use the PowerShell comparison operator "-ge" with date values.Boolean ReferenceEquals(Object objA.DateTime Parse(String s). static System. The System. String format. ParseExact Method static System.. you should use Parse() to convert a date string into a real DateTime object and the current locale: [System. Object objB) SpecifyKind Method static System. ReferenceEquals Method static System.Boolean TryParseExact(String s.Boolean TryParse(String s..DateTime SpecifyKind(DateTime value. March 12..DateTime]::isLeapYear($x) ) { "$x is a leap year!" } } 2000 is a leap year! 2004 is a leap year! 2008 is a leap year! .. 1999") Friday. DateTime t2) op_Inequality Method static System..Boolean op_Inequality(DateTime d1. There are a lot of method names starting with "op_.. DateTime d2) op_LessThan Method static System. DateTimeKind kind) TryParse Method static System.. DateTime d2) op_GreaterThan Method static System.get_UtcNow Method static System.Boolean op_GreaterThanOrEqual(DateTime t1.Boolean op_LessThan(DateTime t1.. DateTime t2) op_Subtraction Method static System. DateTime t2) op_LessThanOrEqual Method static System.DateTime]::Parse("March 12..DateTime]::isLeapYear(2010) False for ($x=2000. IForm. static. 1999 00:00:00 You could easily find out whether a certain year is a leap year: [System. Parse Method static System. sta. TimeSpan t)." with "op" standing for "operator.DateTime get_UtcNow() IsLeapYear Method static System. For example.Boolean op_LessThanOrEqual(DateTime t1.DateTime op_Subtraction(DateTime d.DateTime class supplies you with a bunch of important date and time methods.Boolean op_GreaterThan(DateTime t1.DateTime op_Addition(DateTime d. Of course.7) [Math]::Cos(90) [Math]::Cosh(90) $a = 0 Calculates the quotient of two numbers and returns the remainder in [Math]::DivRem(10.[DateTime]::now Days : 74 Hours : 6 Minutes : 28 Seconds : 49 Milliseconds : 215 Ticks : 64169292156000 TotalDays : 74. • The two timestamps are subtracted from each other using the subtraction operator (" -"). Returns the smallest integer greater than or equal to the specified number. you must specify the desired object type in square brackets. Important: Converting a String to a DateTime this way always uses the U. Returns the angle whose sine is the specified number. Returns the hyperbolic cosine of the specified angle. For it to become a DateTime object.2700140694444 TotalHours : 1782.82026 TotalSeconds : 6416929. 6) [Math]::Ceiling(5. Returns the angle whose tangent is the specified number. Returns the angle whose tangent is the quotient of two specified numbers. locale.48033766667 TotalMinutes : 106948.3. $a . you could have called the static method yourself and received the same result: [DateTime]::op_Subtraction("12/24/2007 18:00".Or you'd like to tell your children with absolute precision how much time will elapse before they get their Christmas gifts: [DateTime]"12/24/2007 18:00" . which is needed for this operator. 15) [Math]::BigMul(1gb. you'll find a lot of useful mathematical methods. To convert a String to a DateTime using your current locale.6) [Math]::Atan(90) [Math]::Atan2(90.6 Two dates are being subtracted from each other here so you now know what happened during this operation: The first time indication is actually text. Returns the cosine of the specified angle. Returns the angle whose cosine is the specified number. This was possible because the DateTime class defined the op_Subtraction() static method. Calculates the complete product of two 32-bit numbers. which returns the current time as DateTime object.2156 TotalMilliseconds : 6416929215. Function Abs Acos Asin Atan Atan2 BigMul Ceiling Cos Cosh DivRem Description Returns the absolute value of a specified number (without signs). Example [Math]::Abs(-5) [Math]::Acos(0.Math class. In the System. [DateTime]::Now) Now it's your turn.6) [Math]::Asin(0. you can use the Parse() method as shown a couple of moments ago! • The second time comes from the Now static property. This is the same as calling the Get-Date cmdlet (which you'd then need to put in parenthesis because you wouldn't want to subtract the Get-Date cmdlet.[ref]$a) an output parameter.S. Try to put some of these methods to work. but rather the result of the Get-Date cmdlet). Dns]::GetHostByAddress("127.51) [Math]::Sign(-12) [Math]::Sin(90) [Math]::Sinh(90) [Math]::Sqrt(64) [Math]::Tan(45) [Math]::Tanh(45) [Math]::Truncate(5.Net.2) [Math]::Round(5.1 16777343 InterNetwork False False False Using Static Type Members Or you can use System.DNS to resolve host names.NET types: Converting Object Types For example. IEEERemainder Returns the remainder of division of two specified numbers.1' IPAddressToString Address AddressFamily ScopeId IsIPv6Multicast IsIPv6LinkLocal IsIPv6SiteLocal : : : : : : : 127. 12) [Math]::Min(-5.NET Types The .NET framework consists of thousands of types. Sqrt Returns the square root of a specified number. This is an example of a . Returns the largest integer less than or equal to the specified Floor number. Max Returns the larger of two specified numbers. such as GetHostByAddress(): [system. This is an example of accessing a static type method.0. Pow Returns a specified number raised to the specified power. Table 6.Exp Returns the specified power of e (2.2) [Math]::Log(1) [Math]::Log10(6) [Math]::Max(-5.IPAddress to work with IP addresses.7182818). Log10 Returns the base 10 logarithm of the specified number. you can use System. Tanh Returns the hyperbolic tangent of the specified angle. Rounds a value to the nearest integer or to the specified number of Round decimal places. Log Returns the natural logarithm of the specified number.67) Finding Interesting .0.NET type conversion where a string is converted into a System.Net.0. Truncate Calculates the integral part of a number.0.7) [Math]::IEEERemainder(5. and maybe you are getting hungry for more. Min Returns the smaller of two specified numbers.Net. Sinh Returns the hyperbolic sine of the specified angle. Sin Returns the sine of the specified angle. 12) [Math]::Pow(6.0. Tan Returns the tangent of the specified angle.5: Mathematical functions from the [Math] library [Math]::Exp(12) [Math]::Floor(5.0.Net.Net.IPAddress]'127. Sign Returns a value indicating the sign of a number.IPAddress type: [system. Are there other interesting types? There are actually plenty! Here are the three things you can do with .1") . 0." These are invisible methods that create the new object. If it has none. which happens to be January 1.Fullname System. you can either convert an existing object to a new type or create a new instance using New-Object: $datetime = [System.DateTime Creating New Objects with New-Object You can create a .1} Using Dynamic Object Instance Members Or you can derive an instance of a type and use its dynamic members. you will get back a date set to the very first date a DateTime type can represent.com/downloads/powershellplus. you cannot create instances of this type. you can create new objects (instances) that are derived from a specific type.DateTime $datetime.zip" # Save the file to this location: $target = "$home\psplus.0.GetType(). try this: # Download address of a file: $address = "http://www. The DateTime type has one constructor that takes no argument.DateTime]::Parse('1.powershell.2000' $datetime. $target) "File was downloaded!" Creating New Objects Most of the time.WebClient $object. the type needs to have at least one constructor to create a new instance of a type.Fullname System. PowerShell cmdlets deliver objects.1.DateTime] '1. To get new instances.2000') $datetime. For example.t which gives you full access to all type "constructors.DateTime $datetime = Get-Date $datetime.DateTime .Fullname System.GetType(). to download a file from the Internet.HostName -------PCNEU01 Aliases ------{} AddressList ----------{127.1. In addition. 0001: New-Object System.GetType(). If you create a new instance of a DateTime object.zip" # Carry out download: $object = New-Object Net.GetType().DownloadFile($address.NET object with New-Object.Fullname System.DateTime $datetime = New-Object System.DateTime $datetime = [System. ... Int32..1) Monday..DateTime(2000.. you can submit additional arguments by adding argument values as a comma separated list enclosed in parentheses.. Like any other method.....toString() } Void .ctor(SByte*.. which is the type constructor....ctor(Char*) Void ..... To list the available constructors for a type..."...Text. and day: New-Object System. 1803 7:54:38 AM Using Constructors When you create a new object using New-Object. February 07. New-Object is in fact calling a method called ctor. the smallest time unit a computer can process: New-Object System..... Int32) Void ....ctor(Char... .String type..... The next line creates a new instance of a System. you can use the GetConstructors() method available in each type. month... System..Monday....String(".. Int32) Void .. Int32.... Int32.GetConstructors() | ForEach-Object { $_.. and the second a number that specifies how often the character will be repeated.. it will interpret text in quotation marks as a field with nothing but characters ( Char[] ). it can support different argument signatures. you can find out which constructors are offered by the System. Int32. Int32) In fact. yet another constructor is used which interprets the number as ticks. Int32) Void . 2000 12:00:00 AM If you simply add a number.....ctor(Char[].. You just used the last variant: the first argument is the character.. 100) ....... January 01.String objects: [System...String]... For example.String and uses a constructor that accepts a character and a number: New-Object System....Encoding) Void .5....... which a type will support......ctor(Char[]) Void ..... Let's check out how you can discover the different constructors. Int32.. New Objects by Conversion ..DateTime(568687676789080999) Monday...String type to produce System. May 01. PowerShell will use the next to last constructor so if you specify text in quotation marks...ctor(Char*.... there are eight different signatures to create a new object of the System.ctor(SByte*) Void ....ctor(SByte*... 0001 12:00:00 AM You can use a different constructor to create a specific date.. There is one that takes three numbers for year. but placed on the right side of the assignment operator: $value = [DateTime]"November 1. PowerShell will automatically convert it to that type.diagnostics. you can also create entirely new objects without New-Object.FullName System. 2007 00:00:00 PowerShell would first convert the text into a date because of the type specification and then assign it to the variable $value. November 1. which itself remains a regular variable without type specification. 2007" $value Thursday. you can assign other data types to the variable later on. if you enclose the desired . 2007" $date. .String: $date = "November 1. the desired type is put in square brackets again. Here. create an object using New-Object: New-Object system.-------------------.Objects can often be created without New-Object by using type casting instead.eventlog("System") Max(K) Retain OverflowAction Entries Name -----.GetType(). That process is sometimes called "implicit type conversion. First.Eventlog type: The result is an EventLog object representing the System event log.String $date November 1. You've already seen how it's done for variables in Chapter 3: # PowerShell normally wraps text as a System. Because $value is not limited to DateTime types.Diagnostics. 2007 # Use strong typing to set the object type of $date: [System. $value = "McGuffin" Using the type casting.FullName System. PowerShell will require you to use precisely the specified object type for this variable.DateTime]$date = "November 1.GetType(). November 1.EventLog]"System" Max(K) Retain OverflowAction Entries Name -----. the string System is converted into the System. 2007" $date.NET type in square brackets and put it in front of a variable name. If you assign a value to the variable.230 System You could have accomplished the same thing without New-Object: [System.-------------------.-----.Diagnostics.480 0 OverwriteAsNeeded 64.480 0 OverwriteAsNeeded 64." Explicit type conversion works a little different. 2007 00:00:00 So.DateTime $date Thursday.---20.---20.-----.230 System In the second example. ......string](".....String(". a constructor will be chosen that requires no arguments # Your arguments will then be interpreted as a PowerShell subexpression in which # a field will be created # PowerShell will change this field into a System... PowerShell selects the constructor automatically # For the System......... you should use New-Object and specify the arguments for the constructor of your choice. # When casting types.."..... but whenever a type has more than one constructor and you want to select the constructor... ....100) ........."............ 100 Type conversion can also include type arrays (identified by "[]") and can be a multi-step process where you convert from one type over another type to a final type........ you can select the constructor you wish of the type yourself: New-Object System. 100 # If your arguments are not in round brackets.String type.String type # PowerShell changes fields into text by separating elements from each other with whitespace: [system... 100) ........String type: [system........ when can you use New-Object and when type conversion? It is largely a matter of taste... 100 ...."........ and you have no control over which constructor is picked. they will be interpreted as a Field # and the first field element # Cast in the System.So........string]". This is how you would convert string text into a character array: [char[]]"Hello!" H e l l o ! You could then convert each character into integers to get the character codes: [Int[]][Char[]]"Hello World!" 72 97 108 108 111 32 87 101 108 116 . Type conversion will automatically choose one constructor...... # Using New-Object... static System.String GetSetting(String AppN. static Microsoft. you may want to get back some of your beloved VisualBasic methods..Y.String Command() static System. you could make a numeric list out of a numeric array and turn that into a string: [string][char[]](65.90) A.H. P.F.. static System.U..VisualBasic assembly.E.. If you have ever written VBScript scripts. Loading Additional Assemblies: Improved Internet Download To get access to even more functionality. you can load additional assemblies with more types and members. static System..L..Object IIf(Boolean Expression.. static System..... . static System. static System.Boolean ReferenceEquals(Objec.MsgBoxResult M.. such as MsgBox() or InputBox().] GetAllSettings(Stri..S...Object Choose(Double Index..VisualBasic") Once you do that... you have access to a whole bunch of new types: [Microsoft.M.VisualBasic. PowerShell uses the separator in the $ofs automatic variable as a separator between the array elements.P. static System.C.Interaction] | Get-Member -static TypeName: Microsoft.Void DeleteSetting(String App.String InputBox(String Prompt.33 Conversely.Object CreateObject(String Pr.assembly]::LoadWithPartialName("Microsoft.. static System....90) A B C D E F G H I J K L M N O P Q R S T U V W X Y Z $OFS = ".D. static System...Void SaveSetting(String AppNa. static System. which is located in the global assembly cache: # Load required assembly: [void][reflection. static System.Object GetObject(String PathN.Object switch(Params Object[].X.J. static System.. static System.VisualBasic.K..Interaction Name ---AppActivate Beep CallByName Choose Command CreateObject DeleteSetting Environ Equals GetAllSettings GetObject GetSetting IIf InputBox MsgBox Partition ReferenceEquals SaveSetting Shell switch MemberType ---------Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Method Definition ---------static System.Z Just remember: if arrays are converted into a string...String[.VisualBasic..Q.R..." [string][char[]](65.Object CallByName(Object Obje.Int32 Shell(String PathName.Void AppActivate(Int32 Proces. static System..G.N.Boolean Equals(Object objA.T.O.String Partition(Int64 Number. Simply load the Microsoft.I. ...B.V. static System.Void Beep() static System....String Environ(Int32 Expressi.W. static System. O.. as always. which is stored in the registry. "Name".VisualBasic. Let's look at its methods: # Make the methods of the COM objects visible: $object | Get-Member -memberType *method TypeName: System.__ComObject#{41904400-be18-11d3-a28b-00104bd35090} Name ---AppActivate MemberType Definition ---------.Network $object. $true. if you want to look up COM objects available on your computer.NET.GetValue("")} How Do You Use COM Objects? Once you know the ProgID of a COM component.com/powershellplus" # This is where the file should be saved: $target = "$home\psplus.Interaction]::InputBox("Enter Name". you can use New-Object to put it to work in PowerShell. which work similar to .NET types and objects.Shell You'll get an object which behaves very similar to . So. which shows a progress bar while downloading files from the Internet: # Reload required assembly: [void][reflection. "$env:username") Tobias Or. $true.Devices. PowerShell can also load and access most COM objects.DownloadFile($address. but use an older technology. you can use a much-improved download method. Which COM Objects Are Available? COM objects each have a unique name. known as ProgID or Programmatic Identifier.VisualBasic") # Download address of a file: $address = "http://www. Get-Member finds all object members for you. It will contain properties with data and methods that you can execute.zip" # Download will be carried out: $object = New-Object Microsoft. "".assembly]::LoadWithPartialName("Microsoft. Variant) .idera. Just specify the additional parameter -COMObject: $object = New-Object -ComObject WScript. $target. "DoNothing") Using COM Objects In addition to . you can visit the registry: Dir REGISTRY::HKEY_CLASSES_ROOT\CLSID -include PROGID -recurse | foreach {$_.NET objects.---------Method bool AppActivate (Variant.VisualBasic. 500. "".[microsoft. And. exe using WScript.TargetPath = 'powershell. Variant. Variant.__ComObject#{f935dc23-1cf0-11d0-adb9-00c04fd58a0b} Name ---Load Save Arguments Description FullName Hotkey IconLocation RelativePath TargetPath WindowStyle WorkingDirectory MemberType ---------Method Method Property Property Property Property Property Property Property Property Property Definition ---------void Load (string) void Save () string Arguments () {get} {set} string Description () {get} {set} string FullName () {get} string Hotkey () {get} {set} string IconLocation () {get} {set} {get} {set} string TargetPath () {get} {set} int WindowStyle () {get} {set} string WorkingDirectory () {get} {set} # We can populate some of the properties $link. Variant) void SendKeys (string. WScript. Variant.WorkingDirectory = $profile $link.lnk") # $link is an object and has the properties and methods $link | Get-Member TypeName: System. but not why the arguments exist. string) int Popup (string. it will be WScript.IconLocation = 'powershell.Application. Variant) int Run (string.Shell. Some of the commonly used COM objects are WScript. InternetExplorer. Only the expected object types are given. Go to a search site of your choice and enter two keywords: the ProgID of the COM components (in this case.Description = 'Launch Windows PowerShell console' $link. Variant.Shell Com object and its method CreateShorcut(): # Create an object: $wshell = New-Object -comObject WScript. string. Scripting. The Internet can help you if you want to know more about a COM command. Word. void RegDelete (string) Variant RegRead (string) void RegWrite (string.Environment]::GetFolderPath('Desktop') # Create a link object $link = $wshell.Application.Application.exe' $link.CreateShortcut Exec ExpandEnvironmentStrings LogEvent Popup Variant) RegDelete RegRead RegWrite Run SendKeys Method Method Method Method Method Method Method Method Method Method IDispatch CreateShortcut (string) IWshExec Exec (string) string ExpandEnvironmentStrings (string) bool LogEvent (Variant.exe' # And save the changes using Save() method $link.Shell) and the name of the method that you want to use.Save() . Let‘s create a shortcut to powershell. Variant) The information required to understand how to use a method may be inadequate.CreateShortcut("$path\PowerShell.Network. and Shell.Shell # Assign a path to Desktop to the variable $path $path = [system.FileSystemObject. NET framework. then you can pass the object to Format-List and type an asterisk after it. which PowerShell is layered. The Get-Member cmdlet retrieves even more data. if you save a command‘s result in a variable. regular console commands. If you cannot perform a task with the cmdlets. you can resort to the unmanaged world outside the . There are a number of condition constructs in the PowerShell language which that we will look at in this chapter. the foundation of the . which both form the members of the object. However. methods are executable commands. you will get a handle on the original objects and can evaluate their properties or call for their commands. or use COM components. In the second part. You can directly access the low-level API functions. you'll employ conditions to execute PowerShell instructions only if a particular condition is actually met.NET framework. While properties store data. there are also static methods. Topics Covered: Creating Conditions o Table 7. Aside from the objects that PowerShell commands provide to you as results. or methods of the . This allows all —not only the most important—properties to be output as text. which are provided directly by the class from which objects are also derived. Conditions are what you need to make scripts clever. Along with the dynamic methods furnished by objects. If you would like to see all of an object‘s properties.NET framework. All the objects that you will work with in PowerShell originate from .NET framework and gain access to a powerful arsenal of new commands.Summary Everything in PowerShell is represented by objects that have exactly two aspects: properties and methods. you can also invoke objects directly from the .1: Comparison operators o Carrying Out a Comparison o "Reversing" Comparisons o Combining Comparisons Table 7.NET framework. Conditions can evaluate a situation and then take appropriate action.2: Logical operators o Comparisons with Arrays and Collections Verifying Whether an Array Contains a Particular Element Where-Object o Filtering Results in the Pipeline o Putting a Condition If-ElseIf-Else Switch o Testing Range of Values o No Applicable Condition o Several Applicable Conditions o Using String Comparisons Case Sensitivity Wildcard Characters Regular Expressions . Objects are the result of all PowerShell commands and are not converted to readable text until you output the objects to the console. enabling you to output detailed information on the properties and methods of any object. 2. When you hit (enter)). -ine -gt. -cne. -cle. 4 -eq 10 False "secret" -ieq "SECRET" True As long as you compare only numbers or only strings. To explicitly specify whether case should be taken into account. comparisons are straight-forward: 123 -lt 123. o Processing Several Values Simultaneously Summary Creating Conditions A condition is really just a question that can be answered with yes (true) or no (false). -cgt. -ige -lt. -inotcontains Conventional = <> > >= < <= Description equals not equal greater than greater than or equal to less than less than or equal to contains Example 10 -eq 15 10 -ne 15 10 -gt 15 10 -ge 15 10 -lt 15 10 -le 15 1.1: Comparison operators PowerShell doesn't use traditional comparison operators that you may know from other programming languages. -ceq. and then the second value that you want to compare with the first. the "=" operator is an assignment operator only in PowerShell. then a comparison operator. Carrying Out a Comparison To get familiar with comparison operators.5 True . The following PowerShell comparison operators allow you to compare values. -ccontains. The result is always True (condition is met) or False (condition not met). -ilt -le. -clt. In particular. -icontains -notcontains. -igt -ge. There are three variants of all comparison operators.3 -notcontains 1 $false Table 7. while ">" and "<" operators are used for redirection. you can use variants that begin with "c" (case-sensitive) or "i" (case-insensitive). PowerShell executes the comparison. The basic variant is case-insensitive so it does not distinguish between upper and lower case letters (if you compare text). enter a value.3 -contains 1 Result $false $true $false $false $true $true $true does not contain 1. -ieq -ne. Operator -eq. -ile -contains. -cnotcontains.2. you can play with them in the interactive PowerShell console! First. -cge. but not in the comparison itself.4" False 123 –lt "123.However. The following conditional statement would evaluate to true only if both comparisons evaluate to true: ( ($age -ge 18) -and ($sex -eq "m") ) . It will always look at the data type to the left of the comparison operator and then try and convert the value to the right to this data type. Logical operators are always interested in the result of a comparison. Combining Comparisons You can combine several comparisons with logical operators because every comparison returns either True or False. "Reversing" Comparisons With the logical operator -not you can reverse comparison results. However. Instead of -not. these results are not always as straight-forward as the previous one: 12 -eq "Hello" False 12 -eq "000012" True "12" -eq 12 True "12" -eq 012 True "012" -eq 012 False 123 –lt 123. That's why the comparison should always be in parentheses.4 True 123 –lt "123. you can also compare different data types. It will expect an expression on the right side that is either true or false. you can also use "!": $a = 10 $a -gt 5 True -not ($a -gt 5) False # Shorthand: instead of -not "!" can also be used: !($a -gt 5) False You should make good use of parentheses if you're working with logical operators like –not.5" True Are the results surprising? When you compare different data types. PowerShell will try to convert the data types into one common data type. 1 to compare single values. comparison operators work pretty much as a filter and return a new array that only contains the elements that matched the comparison. you can use -ne (not equal) operator: 1. # -eq returns only those elements matching the criterion: .4.3.2: Logical operators Comparisons with Arrays and Collections Up to now.2. but not both False True Reverses the result Right Value False True False True False True False True True False True False True (not applicable) False Result False False False True True True False True False False True True False True -or -xor -not Table 7.2.2.3. -eq provides matching array elements only. you‘ve already become familiar with arrays. 1.3.1 -eq 3 3 3 If you'd like to see only the elements of an array that don't match the comparison value. verify whether a certain value exists in an array.3. you've only used the comparison operators in Table 7.4. In Chapter 4.2. Operator Description -and Left Value True False Both conditions must be met False True True False At least one of the two conditions must be met False True True False One or the other condition must be met. How do comparison operators work on arrays? Which element of an array is used in the comparison? The simple answer is all elements! In this case. -contains and -notcontains.You should put separate comparisons in parentheses because you only want to link the results of these comparisons and certainly not the comparisons themselves.1 -ne 3 1 2 4 2 1 Verifying Whether an Array Contains a Particular Element But how would you find out whether an array contains a particular element? As you have seen. the results of a command are handed over to the next one and the Where-Object cmdlet will work like a filter.3 -contains 5 False 1. you will need to set up the appropriate comparison term.2.2.3 -notcontains 5 True Where-Object In the pipeline. If you would like to find out currently running instances of Notepad.1. You will first need to know the names of all the properties found in process objects. MainWindowHandle : 0 MainWindowTitle : MainModule : . To make this work.3 –eq 5 # -contains answers the question of whether the sought element is included in the array: 1. you can specify your condition to Where-Object. Filtering Results in the Pipeline The cmdlet Get-Process returns all running processes.2. allowing only those objects to pass the pipeline that meet a certain condition. Here is one way of listing them: Get-Process | Select-Object -first 1 * __NounName : process Name : agrsmsvc Handles : 36 VM : 21884928 WS : 57344 PM : 716800 NPM : 1768 Path : Company : CPU : FileVersion : ProductVersion : Description : Product : Id : 1316 PriorityClass : HandleCount : 36 WorkingSet : 57344 PagedMemorySize : 716800 PrivateMemorySize : 716800 VirtualMemorySize : 21884928 TotalProcessorTime : BasePriority : 8 ExitCode : HasExited : ExitTime : Handle : MachineName : . 05 7812 notepad Here are two things to note: if the call does not return anything at all. Get-Process already supports a parameter called -name.----------.-------------.MaxWorkingSet MinWorkingSet Modules NonpagedSystemMemorySize NonpagedSystemMemorySize64 PagedMemorySize64 PagedSystemMemorySize PagedSystemMemorySize64 PeakPagedMemorySize PeakPagedMemorySize64 PeakWorkingSet PeakWorkingSet64 PeakVirtualMemorySize PeakVirtualMemorySize64 PriorityBoostEnabled PrivateMemorySize64 PrivilegedProcessorTime ProcessName ProcessorAffinity Responding SessionId StartInfo StartTime SynchronizingObject Threads UserProcessorTime VirtualMemorySize64 EnableRaisingEvents StandardInput StandardOutput StandardError WorkingSet64 Site Container : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : 1768 1768 716800 24860 24860 716800 716800 2387968 2387968 21884928 21884928 716800 agrsmsvc True 0 System. If you're just looking for the processes of the Notepad.name -eq 'notepad' } Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------. Before you make the effort and use Where-Object to filter results. your condition is: name -eq 'notepad: Get-Process | Where-Object { $_.----------68 4 1636 8744 62 0. the name of a process can be found in the Name property. then there are probably no Notepad processes running.Diagnostics.ProcessStartInfo {1964. For example. you should make sure the initial cmdlet has no parameter to filter the information you want right away. which will return only the processes you specify: Get-Process -name notepad Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName .14 7732 notepad 68 4 1632 8764 62 0. 1000} 21884928 False 57344 Putting Together a Condition As you can see from the previous output. you can always add the parameter -ErrorAction SilentlyContinue.----------7732 notepad 7812 notepad The only difference with the latter approach: if no Notepad process is running.... and company name: Get-Process | Where-Object { $_. Microsoft Corporation ehtray Media Center Tray Applet Microsoft Corporation EXCEL Microsoft Office Excel Microsoft Corporation explorer Windows-Explorer Microsoft Corporation GrooveMonitor GrooveMonitor Utility Microsoft Corporation ieuser Internet Explorer Microsoft Corporation iexplore Internet Explorer Microsoft Corporation msnmsgr Messenger Microsoft Corporation notepad Editor Microsoft Corporation notepad Editor Microsoft Corporation sidebar Windows-Sidebar Microsoft Corporation taskeng Task Scheduler Engine Microsoft Corporation WINWORD Microsoft Office Word Microsoft Corporation wmpnscfg Windows Media Player Network S. description.14 0.company -like 'micro*' } | Select-Object name. Get-Process throws an exception. The $_ variable contains the current pipeline object. Microsoft Corporation . which will work for all cmdlets and hide all error messages.----8744 62 8764 62 -----0. you'll see that your condition is specified in curly brackets after the cmdlet. description. company Name Description Company -------------------conime Console IME Microsoft Corporation dwm Desktopwindow-Manager Microsoft Corporation ehmsas Media Center Media Status Aggr. Where-Object is much more flexible because it can filter on any piece of information found in an object. When you revisit your Where-Object line. If you don't like that. Microsoft Corporation wpcumi Windows Parental Control Notif.05 -. telling you that there is no such process. You can use the next one-liner to retrieve all processes whose company name begins with "Micro" and output name. While sometimes the initial cmdlet is able to do the filtering all by itself (like in the previous example using -name)..------68 68 -----4 4 ----1636 1632 ----... . Here. instead of WhereObject. it does make your code a bit unreadable: # The two following instructions return the same result: all running services Get-Service | Foreach-Object {$_.Status -eq 'Running' } If-ElseIf-Else Where-object works great in the pipeline. the comparison is true. the code in the curly brackets after it will be executed. In the simplest case. though. there is an alias for Where-Object: "?". even when the condition isn't met. and so the code in the curly brackets wasn't executed. If you have several conditions. this code will be executed} The condition must be enclosed in parentheses and follow the keyword If. The condition was not met. To get an answer. because you would like to always get a result. the statement will look like this: If (condition) {# If the condition applies..ElseIf. the code in the curly brackets after If is executed if the condition is met.Since you will often need conditions in a pipeline. but it is inappropriate if you want to make longer code segments dependent on meeting a condition. You can expand the If statement with Else to accomplish that: if ($a -gt 10) { "$a is larger than 10" } else { "$a is less than or equal to 10" } Now. So. Try it out: If ($a -gt 10) { "$a is larger than 10" } It's likely. If the condition is met. However. if the preceding condition isn‘t true. that clearly shows that the simplest If statement usually doesn't suffice in itself. you can make sure that the condition is met: $a = 11 if ($a -gt 10) { "$a is larger than 10" } 11 is larger than 10 Now. otherwise. you may insert as many ElseIf blocks between If and Else as you like: if ($a -gt 10) { "$a is larger than 10" } elseif ($a -eq 10) { .Status -eq 'Running' } Get-Service | ? {$_. However. the If. that you won't (yet) see a result. you can also use "?'".Else statement works much better. it will not. the code in the curly brackets after Else will be executed. and the If statement ensures that the code in the curly brackets will return a result. As it is. If condition evaluates $true.". Switch . then you should separate the commands with a semi-colon ". In addition." } Time for break! This example shows that the condition after If must always be in parentheses. What happens if several conditions are true? Then the code after the first applicable condition will be executed and all other applicable conditions will be ignored. All that the If statement evaluates is $true or $false. But the value could come from another function or from a variable: # Returns True from 14:00 on. it will not. If you'd like to execute more than one command in the curly brackets without having to use new lines. the code in the curly brackets after it will be executed. Conditions are only a way to return one of the requested values $true or $false. otherwise."$a is exactly 10" } else { "$a is less than 10" } The If statement here will always execute the code in the curly brackets after the condition that is met.Hour -gt 13 } isAfternoon True # Result of the function determines which code the If statement executes: if (isAfternoon) { "Time for break!" } else { "It’s still early. you can also write the If statement in a single line. otherwise False: function isAfternoon { (get-date). if ($a -gt 10) { "$a is larger than 10" } elseif ($a -eq 10) { "$a is exactly 10" } elseif ($a –ge 10) { "$a is larger than or equal to 10" } else { "$a is smaller than 10" } The fact is that the If statement doesn't care at all about the condition that you state. The code after Else will be executed when none of the preceding conditions are true. but it can also come from any source as long as it is $true or $false. the action associated with that condition is then performed. You can create your own condition and put it in curly brackets. to verify equality. a code block is used that results in True for numbers smaller than 5: {$_ -le 5} { "Number from 1to 5" } # A value is used here. The condition must then result in either true or false: $value = 8 switch ($value) { # Instead of a standard value.If you'd like to test a value against many comparison values. The Switch code is much cleaner: # Test a value $value = 1 if ($value -eq { " Number 1" } elseif ($value { " Number 2" } elseif ($value { " Number 3" } Number 1 # Test $value switch { 1 { 2 { 3 { } Number against several comparison values (with If statement): 1) -eq 2) -eq 3) a value against several comparison values (with Switch statement): = 1 ($value) "Number 1" } "Number 2" } "Number 3" } 1 This is how you can use the Switch statement: the value to switch on is in the parentheses after the Switch keyword. That value is matched with each of the conditions on a case-by-case basis. Switch checks whether this value matches $value: 6 { "Number 6" } # Complex conditions areallowed as they are here. Testing Range of Values The default comparison operator in a switch statement is -eq. the –eq operator. You can use the default comparison operator. If a match is found. the If statement can quickly become unreadable. where –and is used to combine two comparisons: {(($_ -gt 6) -and ($_ -le 10))} { "Number from 7 to 10" } } . but you can also compare a value with other comparison statements. you can combine any PowerShell statements in the code block and also use the logical operators listed in Table 7. For If. Switch will execute them both whereas If had only executed the first matching condition code. then Switch will work differently from If. you can use the initial value stored in $_ for your conditions. the Switch clause will execute all code for all conditions that are met. which serves as a catch-all. but because $_ is generally available anywhere in the Switch block.Number from 7 to 10 The code block {$_ -le 5} includes all numbers less than or equal to 5. all applicable conditions are executed: $value = 50 switch ($value) { . The code block {(($_ -gt 6) -and ($_ -le 10))} combines two conditions and results in true if the number is either larger than 6 or less than-equal to 10. For Switch. Here. If no condition is met. if there are two conditions that are both met. the If clause will provide the Else statement. To change the Switch default behavior and make it execute only the first matching code. you should use the statement continue inside of a code block. Likewise. Switch has a similar catch-all called default: $value = 50 switch ($value) { {$_ -le 5} { "$_is a number from 1 to 5" } 6 { "Number 6" } {(($_ -gt 6) -and ($_ -le 10))} { "$_ is a number from 7 to 10" } # The code after the next statement will be executed if no other condition has been met: default {"$_ is a number outside the range from 1 to 10" } } 50 is a number outside the range from 1 to 10 Several Applicable Conditions If more than one condition applies. So. only the first applicable condition was executed. Consequently. you could just as well have put it to work in the result code: $value = 8 switch ($value) { # The initial value (here it is in $value) is available in the variable $_: {$_ -le 5} { "$_ is a number from 1 to 5" } 6 { "Number 6" } {(($_ -gt 6) -and ($_ -le 10))} { "$_ is a number from 7 to 10" } } 8 is a number from 7 to 10 No Applicable Condition In contrast to If.2. you may get more than one result. However." } "print" { "I print. break} } The number 50 The keyword break tells PowerShell to leave the Switch construct. So in some circumstances. they work differently. you can use the –case option. but assign 50. a different action will be performed. In loops.. you can add the continue or break statement to the code. $value = 50 switch ($value) { 50 { "the number 50".. Using String Comparisons The previous examples have compared numbers.. continue would only exit the current iteration. all applicable conditions will ensure that the following code is executed.0 to $value. the other two conditions continue to remain fulfilled. While breaks exits a loop immediately.50 { "the number 50" } {$_ -gt 10} {"larger than 10"} {$_ -is [int]} {"Integer number"} } The Number 50 Larger than 10 Integer number Consequently. You could also naturally compare strings since you now know that Switch uses only the normal –eq comparison operator behind the scenes and that their string comparisons are also permitted. Try out that example. If you'd like to receive only one result. As such. Case Sensitivity Since the –eq comparison operator doesn't distinguish between lower and upper case. In this case. break and continue are interchangeable. Working behind the .. break} {$_ -is [int]} {"Integer number". you'll get just two results instead of three. case sensitivity doesn't play a role in comparisons.. break } {$_ -gt 10} {"larger than 10". In conditions.. The following code could be the basic structure of a command evaluation." } "open" { "I open." } Default { "Unknown command" } } I save.. If you want to distinguish between them. depending on the specified command: $action = "sAVe" switch ($action) { "save" { "I save. Do you know why? That's right: the third condition is no longer fulfilled because the number in $value is no longer an integer number... *?\b" { " The text contains the string 'dress' in arbitrary locations: $($matches[0])" } .10.10.10.10" switch -wildcard ($text) { "IP*" { "The text begins with IP: $_" } "*.10..scenes. you can activate the -like operator.\d{1. This way... you can ensure that Switch uses the –match comparison operator instead of –eq. you can also exchange a standard comparison operator for –like and –match operators and then carry out wildcard comparisons. discussion of regular expression in greater detail.3}\. with the "*" wildcard character: $text = "IP address: 10." } "open" { "I open. among others. With the -regex option.*" { "The text contains an IP address string pattern: $_" } "*dress*" { "The text contains the string 'dress' in arbitrary locations: $_" } } The text begins with IP: IP address: 10. which is why you should take a peek ahead at Chapter 13.. Using the –wildcard option." } Default { "Unknown command" } } Unknown command Wildcard Characters In fact.*. you can even parse information out of the text: $text = "IP address: 10. and thus employs regular expressions.10.10 The text contains the string 'dress' in arbitrary locations: IP address: 10. it will replace the –eq comparison operator with –ceq.10 Regular Expressions Simple wildcard characters ca not always be used for recognizing patterns.10.10" switch -regex ($text) { "^IP" { "The text begins with IP: $($matches[0])" } "\d{1.*.*?dress. you will usually get back the text that matches the pattern in the $matches variable.. Regular expressions are much more efficient. you can identify a pattern much more precisely than by using simple wildcard characters.10. But that's not all!.. As in the case with the –match operator. which is conversant.\d{1. But they assume much more basic knowledge.3}\.10. Using regular expressions.3}" { "The text contains an IP address string pattern: $($matches[0])" } "\b.3}\.\d{1.10." } "print" { "I print.10. after which case sensitivity will suddenly become crucial: $action = "sAVe" switch -case ($action) { "save" { "I save.10 The text contains an IP address string pattern: IP address: 10. name. If the WS property of a process is larger than one megabyte. 4 is even. you can pass to Switch the values in an array or a collection. only the first result you got by using $matches[0] should interest you."} } 1 is uneven..5 switch ($array) { {$_ % 2} { "$_ is uneven. To do so. The next line queries Get-Process for all running processes and then pipes the result to a script block ( & {."} Default { "$_ is even. but also entire arrays and collections. Switch is passed an array containing five elements.10. In the following example. depending on their form. this process is output.StartsWith("a")} { $_ } }} . Processing Several Values Simultaneously Until now. This difference is especially striking for elaborate commands: # Switch returns all files beginning with "a": Dir | & { switch($Input) { {$_. one by one: $array = 1. 5 is uneven. Switch would be an ideal candidate for evaluating results on the PowerShell pipeline because the pipeline character ("|") is used to forward results as arrays or collections from one command to the next. The entire expression is embedded in $(. return several results.WS -gt 1MB} { $_ }}} However.. Switch will automatically take all the elements. a hash table with each result.) to ensure that this result appears in the output text. In Where-Object.. As such.}).. Switch will then filter all of the processes whose WS property is less than or equal to one megabyte: Get-Process | & { Switch($input) { {$_.} The text begins with IP: IP The text contains an IP address string pattern: 10. this line is extremely hard to read and seems complicated. 2 is even. Switch will evaluate the result of the pipeline. But Switch can also process several values at the same time.. it processes the results of the preceding command precisely when the results are ready. You can formulate the condition in a much clearer way by using Where-Object: Get-Process | Where-Object { $_. you have always passed just one value for evaluation to Switch.. There you have it: Switch will accept not only single values. 3 is uneven. In this example. one at a time. which is available in $input.10. In the script block.WS -gt 1MB } This variant also works more quickly because Switch had to wait until the pipeline has collected the entire results of the preceding command in $input.10 The text contains the string 'dress' in arbitrary locations: IP address The result of the –match comparison with the regular expression is returned in $matches. from the array and compare each of them. because regular expressions can. you can use the If statement.2 to form complex queries. which in their simplest form can be reduced to plain Yes or No answers. Topics Covered: ForEach-Object o Invoking Methods Foreach Do and While o Continuation and Abort Conditions o Using Variables as Continuation Criteria o Endless Loops without Continuation Criteria For o For Loops: Just Special Types of the While Loop o Unusual Uses for the For Loop Switch Exiting Loops Early o Continue: Skipping Loop Cycles o Nested Loops and Labels Summary . then certain code segments will be executed.1.# But it doesn't do so until Dir has retrieved all data. and that can take a long time: Dir -Recurse | & { switch($Input) { {$_. or would like to execute larger code segments independently of conditions.name. In this chapter. Switch is the right choice when you want to check a particular variable against many different possible values.StartsWith("a") } # The alias of Where-Object ("?") works exactly the same way: Dir -recurse | ? { $_. This is the typical "If-Then" scenario: if certain conditions are met.StartsWith("a") } Summary Intelligent decisions are based on conditions. which evaluates as many different conditions as you wish and. you will learn the PowerShell loop constructs. will then execute the allocated code. you can formulate such conditions and even combine these with the logical operators listed in Table 7. depending on the result. Using it. you can compare a fixed initial value with various possibilities. allowing only those results through the pipeline that correspond to your condition. If you would like more control.StartsWith("a")} { $_ } }} # Where-Object processes the incoming results immediately: Dir -recurse | Where-Object { $_. An alternative to the If statement is the Switch statement. you can use the Where-Object cmdlet in the pipeline. Using the comparison operators listed in Table 7. Loops repeat PowerShell code and are the heart of automation. The simple Yes/No answers of your conditions will determine whether particular PowerShell instructions can carried out or not.name.name. In their simplest form. It functions there like a filter. 10 | Foreach-Object { notepad } Foreach-Object is simply a cmdlet. a dialog appears asking the user to save it first: Get-Process notepad | ForEach-Object { $_. CPU. All you needed to do is calculate the cut-off date using New-Timespan.10 | Foreach-Object { notepad. If there is unsaved data. StartTime.Minutes = New-Timespan $_. "Launching Notepad!" } In PowerShell editor. This will give you much more control. you can invoke methods of these objects. Time" } Most of the time. you can take advantage of the property StartTime..StartTime | Select-Object -expandproperty TotalMinutes . You could use Stop-Process to stop a process.ForEach-Object Many PowerShell cmdlets return more than one result object.10 | Foreach-Object -process { notepad } Inside of the script block. You can use a Pipeline loop: foreach-object to process them all one after another.CPU } Invoking Methods Because ForEach-Object will give you access to each object in a pipeline... The next line will launch 10 instances of the Notepad editor: 1. you can easily use this loop to repeat the code multiple times. you can execute any code.. you can use multiple lines: 1. The next line closes all instances of Notepad windows. and the script block following it really is an argument assigned to ForeachObject: 1. In fact.Name.10 | Foreach-Object { notepad "Launching Notepad!" } The element processed by the script block is available in the special variable $_: 1. you will not feed numbers into Foreach-Object... Let's first get a listing that tells you how many minutes an instance of Notepad has been running: Get-Process notepad | ForEach-Object { $info = $_ | Select-Object Name. But if you want to close programs gracefully. $_. you learned how to take advantage of this to close all instances of the Notepad. In Chapter 7. but instead the results of another cmdlet.10 | Foreach-Object { "Executing $_. you should provide the user with the opportunity to save unsaved work by also invoking the method CloseMainWindow(). Have a look: Get-Process | Foreach-Object { 'Process {0} consumes {1} seconds CPU time' -f $_.CloseMainWindow() } You can also solve more advanced problems. You can also execute multiple lines of code. You can use a semicolon to separate statements from each other in one line: 1. Minutes $info. If you want to close only those instances of Notepad that were running for more than 10 minutes. What you see here is a Foreach-Object loop with an If condition. Don't forget to output the $info object at the end or the script block will have no result. Instead. In the above code. That's why it is safe to process even large sets of objects. which are not part of the original object. Next. Select-Object will happily add that new property to the object. which calculates the time difference between now and the time found in StartTime.name } # Foreach loop lists each element in a colection: foreach ($element in Dir C:\) { $element.StartTime -lt $cutoff) { $_ } } This code would only return Notepad processes running for more than 10 minutes and you could pipe the result into Stop-Process to kill those. The following line iterates through all files and folders on drive c:\. Don't confuse this with the Foreach alias.name } The true Foreach statement does not use the pipeline architecture. if you see a Foreach statement inside a pipeline. This is the most important difference because it has very practical consequences. this really is a Foreach-Object cmdlet. This is exactly what Where-Object does so if you need loops with conditions to filter out unwanted objects. Note how results are returned immediately: . you will need a condition: Get-Process Notepad | Foreach-Object { $cutoff = ( (Get-Date) . the script block creates a copy of the incoming object using Select-Object. which represents Foreach-Object. To kill only those instances of Notepad that were running for more than 10 minutes. the pipeline processes objects in real time. you can simplify: Get-Process Notepad | Where-Object { $cutoff = ( (Get-Date) . We specified an additional property called Minutes to display the running minutes. So. the Foreach statement iterates over a collection of objects: # ForEach-Object lists each element in a pipeline: Dir C:\ | ForEach-Object { $_. The pipeline has a very low memory footprint because there is always only one object travelling the pipeline.StartTime -lt $cutoff } Foreach There is another looping construct called Foreach.$info } Check out a little trick.(New-Timespan -minutes 10) ) if ($_. While Foreach-Object obtains its entries from the pipeline. we can fill in the information into the Minutes property. which selects the columns you want to view. This is done using New-Timespan. it can only live inside a code block. The true Foreach loop is never used inside the pipeline.(New-Timespan -minutes 10) ) $_. In addition. chances are that your system runs out of memory before it has a chance to process the results. While is the criteria that has to be met at the end of the loop so that the loop can be iterated once again.*. verification of the loop criteria doesn't take place until the end. You must set additional abort conditions to prevent an endless loop to really run endlessly. So. This loop is supposed to re-iterate only if the input is false.9279656 Measure-Command { foreach ($element in (1. The loop will go through its iteration at least once because you have to query the user at least once before you can check the criteria. You must be careful with the following statement: # careful! foreach ($element in Dir C:\ -recurse -erroraction SilentlyContinue) { $element.Dir C:\ -recurse -erroraction SilentlyContinue | ForEach-Object { $_. foreach is much faster than foreach-object because the pipeline has a significant overhead.* pattern.bat") .. the first thing you will notice is that there is no output for a long time. In the example. You could also use regular expressions to refine your verification.FullName } On the other hand. There are also cases in which the criteria needs to be verified at the beginning and not at the end of the loop. It is up to you to decide whether you need memory efficient real-time processing or fast overall performance: Measure-Command { 1.*.10000)) { $element } } | Select-Object -expandproperty TotalSeconds 0. An example would be a text file that you want to read one line at a time. To accomplish this.10000 | Foreach-Object { $_ } } | Select-Object -expandproperty TotalSeconds 0. The file could be empty and the loop should check before its first iteration whether there's anything at all to read. Endless loops are a good idea if you don't know exactly how many times the loop should iterate.io. The loop will then be iterated until the input does not match a Web address. In this type of endless loop. While that's only an approximate verification. Foreach does not work in real time.0391117 Do and While Do and While generate endless loops.FullName } If you tried the same with foreach. it usually suffices. The loop will end when the conditions are met. How long that lasts and how often the query will iterate depends on the user and his ability to grasp what you want. it first collects all results before it starts to iterate. do { $Input = Read-Host "Your homepage" } while (!($Input -like "www..*")) This loop asks the user for his home page Web address. Both procedures will be explained in detail in Chapter 13. Continuation and Abort Conditions A typical example of an endless loop is a user query that you want to iterate until the user gives a valid answer. just put the While statement and its criteria at the beginning of the loop (and leave out Do. -like is used to verify whether the input matches the www. That's why "!" is used to simply invert the result of the condition. which is no longer of any use): # Open a file for reading: $file = [system.file]::OpenText("C:\autoexec. If you tried to enumerate all files and folders on your drive c:\. powershell." } } Your homepage: hjkh Please give a valid web address. give explanation and ask again: Write-Host –Fore "Red" "Please give a valid web address. that makes sense only if you exit the loop in some other way. Of course. give explanation and query again: Write-Host –Fore "Red" "Please give a valid web address.ReadLine() } # Close file again: $file. do { $Input = Read-Host "Your Homepage" if ($Input –like "www. then it won't. The break statement can be used for this: while ($true) { $Input = Read-Host "Your homepage" if ($Input –like "www.# Continue loop until the end of the file has been reached: while (!($file. Conditions are therefore not mandatory. no further query: $furtherquery = $false } else { # Input incorrect.close Using Variables as Continuation Criteria The truth is that the continuation criteria after While works like a simple switch." $furtherquery = $true } } while ($furtherquery) Your Homepage: hjkh Please give a valid web address. You could just as well have presented the loop with a variable instead of a comparison operation.*. but simply provide the required $true or $false.*") { # Input correct.*.powershell. then the loop will be iterated.com . which will never stop on its own.*") { # Input correct. if it is $false. as long as the variable contained $true or $false. Your Homepage: www.EndOfStream)) { # Read and output current line from the file: $file. If the expression is $true. Your homepage: www. no further query: break } else { # Input incorrect. The loop will then become a genuinely endless loop.com Endless Loops without Continuation Criteria You can also omit continuation criteria and instead simply use the fixed value $true after While. and to change a control variable with a particular increment at every iteration of the loop. but it is not responsible for iterating. the continuation criteria: # First expression: simple While loop: $i = 0 while ($i –lt 5) { $i++ $i } 1 2 3 4 5 # Second expression: the For loop behaves like the While loop: $i = 0 for (. The following loop will output a sound at various 100ms frequencies (provided you have a soundcard and the speaker is turned on): # Output frequencies from 1000Hz to 4000Hz in 300Hz increments for ($frequency=1000. the loop will iterate. as well as which increments will be used for counting. If this expression is $true. in contrast to the While loop. to verify whether a final value is achieved. you'll quickly notice that it is actually only a specialized form of the While loop. Increment: The third expression is likewise re-evaluated with every looping. A For loop can become a While loop if you ignore the first and the second expression and only use the second expression.For You can use the For loop if you know exactly how often you want to iterate a particular code segment. For loops are counting loops. Continuation criteria: The second expression is evaluated before every iteration.Console]::Beep($frequency. evaluates not only one. $frequency +=300) { [System. You can specify the number at which the loop begins and at which number it will end to define the number of iterations.) { $i++ $i } 1 2 . but three expressions: Initialization: The first expression is evaluated when the loop begins.$i -lt 5. $frequency –le 4000. The For loop. Of course. It basically corresponds to the continuation criteria of the While loop.100) } For Loops: Just Special Types of the While Loop If you take a closer look at the For loop. Be careful as this expression cannot generate output. it is entirely up to you whether you want to use the For loop solely for this purpose. These three expressions can be used to initialize a control variable. the current line from the text file is read. In the example. the loop is iterated.. the $input variable is set to an empty string. If it is. the first expression of the loop opened the file so it could be read. In the third expression.*"). The first expression of the For loop can be generally used for initialization tasks. the line-by-line reading of a text file can be implemented by a For loop with less code: for ($file = [system. So. This third expression is always executed invisibly. Nothing more needs to be in the loop. you can also use it in the user query example we just reviewed: for ($Input="". The read line is then output in the loop. the contents of the line are output within the loop." } In the first expression. as well as performs different tasks in the loop. !($Input -like "www. For a quick demonstration. !($file. Moreover. $Input = Read-Host "Your homepage") { Write-Host -fore "Red" " Please give a valid web address. That makes Switch one of the most powerful statements in PowerShell. Switch Switch is not only a condition.file]::OpenText("C:\autoexec. In the second expression. take a look at the following simple Foreach loop: $array = 1. it will use "!" to invert the result so that it is $true if an invalid Web address is in $input. In addition. it can evaluate conditions. which means you can't use it to output any text. It will return $true if the end of the file hasn't been reached yet so that the loop will iterate in this case. The second expression checks whether a valid Web address is in $input. but also functions like a loop. In this case.io. it might have been preferable to use the While loop right from the start. The third expression sets the increment of a control variable.close() REM Dummy file for NTVDM In this example. the user is queried for a Web address. The "!" operator inverts the result again. ` $line = $file. In fact.*.3 4 5 Unusual Uses for the For Loop Of course in this case. It certainly makes more sense not to ignore the other two expressions of the For loop.bat"). The third expression reads a line from the file. Switch works almost exactly like the Foreach loop.5 . The third expression of the For loop is executed before every loop cycle.ReadLine()) { # Output read line: $line } $file. an explanatory text is output.EndOfStream). but to use them for other purposes. a check is made whether the end of the file has been reached. In the example. That means Foreach is the right choice if you want to execute exactly the same statements for every loop cycle. . as it can for Foreach. the Switch loop contains only the default statement. but is always called $_. this loop would look like this: $array = 1. What's important here is the other. if you'd like to process each element of an array according to its contents. On the other hand. it would be preferable to use Switch: $array = 1.5 switch ($array) { Default { "Current element: $_" } } Current element: 1 Current element: 2 Current element: 3 Current element: 4 Current element: 5 The control variable that returns the current element of the array for every loop cycle cannot be named for Switch. The code that is to be executed follows it in curly brackets.. Switch can utilize conditions to execute optionally different code for every loop.foreach ($element in $array) { "Current element: $element" } Current element: 1 Current element: 2 Current element: 3 Current element: 4 Current element: 5 If you use switch. take a look at Chapter 7 where you'll find an explanation of how Switch evaluates conditions. In the simplest case. loop-like aspect of Switch. there's an additional difference: while Foreach always executes the same code every time the loop cycles.5 switch ($array) { 1 { "The number 1" } {$_ -lt 3} { "$_ is less than 3" } {$_ % 2} { "$_ is odd" } Default { "$_ is even" } } The number 1 1 is less than 3 1 is odd 2 is less than 3 3 is odd 4 is even 5 is odd If you're wondering why Switch returned this result.. The external part of the loop functions in exactly the same way. Inside the loop. FileInfo] } Nested Loops and Labels Loops may be nested within each other. $entry. . while ($true) { $password = Read-Host "Enter password" if ($password -eq "secret") {break} } Continue: Skipping Loop Cycles The Continue statement aborts the current loop cycle. The second (inner) loop runs through all instances of the respective WMI class. continue immediately with the next element: if (!($entry -is [System. The inner loop checks whether the name of the current instance begins with "a". The following is a little example of how you can ask for a password and then use Break to exit the loop as soon as the password "secret" is entered. $entry.length } } This also works in a pipeline using a Where-Object condition: Dir $env:windir | Where-Object { $_ -is [System.IO.IO.FileInfo]) { "File {0} is {1} bytes large. which is the loop that they were called from. However.Exiting Loops Early You can exit all loops by using the Break statement. However. The next example nests two Foreach loops. you can label loops and then submit the label to continue or break if you want to exit or skip outer loops.IO.name. you can also use a condition to filter out sub-folders: foreach ($entry in Dir $env:windir) { if ($entry -is [System.name." -f $entry.length } Of course. if you do nest loops. This allows you to output all instances of all three WMI classes. but does continue the loop. too." -f $entry. The next example shows you how to abort processing folders and only focus on files returned by Dir: foreach ($entry in Dir $env:windir) { # If the current element matches the desired type. how do Break and Continue work? They will always affect the inner loop. which will give you the additional option of defining additional stop criteria in the loop. The first (outer) loop cycles through a field with three WMI class names.FileInfo])) { continue } "File {0} is {1} bytes large. the Continue statement in the inner loop has had an effect on the inner loop where the statement was contained.__CLASS.name. Once an element was found that begins with "a.exe As expected.StartsWith("a"))) {continue} "{0}: {1}" –f $instance. except now Continue would need to have an effect on the outer loop."Win32_Process") { foreach ($instance in Get-WmiObject $wmiclass) { if (!(($instance. but from an array or a collection. But how would you change the code if you'd like to see only the first element of all services.exe Win32_Process: audiodg." The result is a list of all services.StartsWith("a")) { "{0}: {1}" –f $instance. Foreach is a similar type of loop whose contents do not come from the pipeline. user accounts.toLower())." the outer loop would continue with the next WMI class: :WMIClasses foreach ($wmiclass in "Win32_Service".__CLASS.exe Win32_Process: Ati2evxx."Win32_UserAccount". you would do almost the exact same thing. the inner loop will then invoke Continue skip all instances not beginning with "a.toLower()). user accounts.name continue WMIClasses } } } Win32_Service: AeLookupSvc Win32_UserAccount: Administrator Win32_Process: Ati2evxx.name } } Win32_Service: AeLookupSvc Win32_Service: AgereModemAudio Win32_Service: ALG Win32_Service: Appinfo Win32_Service: AppMgmt Win32_Service: Ati External Event Utility Win32_Service: AudioEndpointBuilder Win32_Service: Audiosrv Win32_Service: Automatic LiveUpdate – Scheduler Win32_UserAccount: Administrator Win32_Process: Ati2evxx. and processes that begins with "a"? Actually."Win32_UserAccount".name. such as to output the data contained in object properties as text or to invoke methods of the object.exe Win32_Process: AppSvc32."Win32_Process") { :ExamineClasses foreach ($instance in Get-WmiObject $wmiclass) { if (($instance.exe Win32_Process: agrsmsvc. .if not. and running processes that begin with "a": foreach ($wmiclass in "Win32_Service".exe Summary The cmdlet ForEach-Object will give you the option of processing single objects of the PowerShell pipeline. $instance.exe Win32_Process: ATSwpNav. $instance. It could look like this: function Get-InstalledSoftware { } Once you enter this code in your script editor and run it dot-sourced. In this chapter. If you saved your code in a file called c:\somescript.ps1' .. parameter types. Switch is a combined Foreach loop with integrated conditions so that you can immediately implement different actions independently of the read element. As such. All loops can exit ahead of schedule with the help of Break and skip the current loop cycle with the help of Continue.ps1. PowerShell learned a new command called Get-InstalledSoftware. Moreover. If you want to do the checking at the end of the loop. Topics Covered: Creating New Functions Defining Function Parameters Adding Mandatory Parameters Adding Switch Parameters Adding Help to your Functions Creating Pipeline-Aware Functions Playing With Prompt Functions Creating New Functions The most simplistic function consists of a name and a script block. you can attach a script block to a name to create your own new commands. define the function body.. the script block executes. Let's create a function that reads installed software from your registry. Functions provide the interface between your code and the user. The simplest type is While. Whenever you call that name. which checks its continuation criteria at the beginning of the loop. In the case of nested loops. much like cmdlets. On the other hand. and even provide help.While and While are designed for loops that have to be iterated as long as the respective situation and running time conditions require it. choose Do…While. This means that For is best suited for loops which need to be counted or must complete a set number of iterations. Functions work pretty much like macros. because it can count loop cycles and automatically terminate the loop after a designated number of iterations. you will learn how to create your own functions. Finally. The For loop is an extended While loop. Switch can step through the contents of text files lineby-line and evaluate even log files of substantial size. They can define parameters. there are endless loops that iterate a code block until a particular condition is met. Do. you will need to run it like this: . 'c:\somescript. you can assign an unambiguous name to the loops and then use this name to apply Break or Continue to nested loops. First.In addition. information may be clipped..exe MsiExec.. defining functions in a script is a better approach because you won't want to enter your functions manually all the time. If you enter "Get-Ins" and then press TAB.exe MsiExec. it will return a sorted list of all the installed software packages.. it will be returned as an array: PS > function test { "One" } PS > test .) DisplayVersion -------------8.3. The script block you attached to your function name was empty.0 ----------MsiExec.If you don't want to use a script.0. (..4. and their uninstall information: PS > Get-InstalledSoftware DisplayName UninstallString -------------64 Bit HP CIO Components Installer /I{5737101A-27C4-40. You may want to enable script execution if you are unable to run a script because of your current ExecutionPolicy settings: Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -force Once you defined your function. You can pipe the results to any of the formatting cmdlets to change because the information returned by your function will behave just like information returned from any cmdlet. Here is the beef to your function that makes it report installed software: function Get-InstalledSoftware { $path = 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Unins tall\*' Get-ItemProperty -path $path | Where-Object { $_. you can even use code completion.. PowerShell will complete your function name.DisplayName -ne $null } | Select-Object DisplayName. If you leave behind more than one piece of information. UninstallString | Sort-Object DisplayName } When you run it. Running a script to define the functions is much more practical.69 2. Note the way functions return their results: anything you leave behind will be automatically assigned as return value.1 3.0.exe As always. Apple Mobile Device Support /I{963BFE7E-C350-43. Bonjour /X{E4F5E48E-7155-4C.... the new command Get-InstalledSoftware won't do anything yet.2. DisplayVersion. you can also enter a function definition directly into your interactive PowerShell console like this: function Get-InstalledSoftware { } However. Of course. their version. You can add whatever code you want to run to make your function do something useful. PowerShell will take care of parameter parsing. I am hungry!' Since the function Speak-Text now supports a parameter. let's try adding some parameters to our function. as abbreviated named parameters.SPVoice). From working with cmdlets. will work without additional information from the user.SPVoice). Let's add some parameters to Get-InstalledSoftware to make it more useful. Adding parameters is very simple.One PS > function test { "Zero". it is easy to submit additional information to the function code.Speak($text) | Out-Null } Your new command Speak-Text converts (English) text to spoken language. such as Get-InstalledSoftware in the previous example.Speak($text) | Out-Null } function Speak-Text { param ($text) (New-Object -com SAPI. you can add more parameters as comma-separated list. "One". Here. You can submit arguments as named parameters.2] One Two PS > $result[-1] Three Defining Function Parameters Some functions. "Two". we add parameters to select the product and when it was installed: . you already know how clever it can be to provide detailed information so the command can return exactly what you want. So. and the same rules apply that you already know from cmdlets. "Three" } PS > test Zero One Two Three PS > $result = test PS > $result[0] Zero PS > $result[1. so you can now try this: Speak-Text 'Hello. Both definitions define the same function: function Speak-Text ($text) { (New-Object -com SAPI. It accesses an internal Text-to-SpeechAPI. You can either add them in parenthesis right behind the function name or move the list of parameters inside your function and label this part param. and as positional parameters: Speak-Text 'This is positional' Speak-Text -text 'This is named' Speak-Text -t 'This is abbreviated named' To submit more than one parameter. 8107.5 SP2 x64 ENU 33 3.0 Microsoft Security Client 119 2. If you want to only find software with "Microsoft" in its name that was installed within the past 180 days. you can submit parameters: PS > Get-InstalledSoftware -name *Microsoft* -days 180 | Format-Table AutoSize DisplayName Days DisplayVersion -------------.0.5. Here is a simple version: function ConvertTo-Euro { .DisplayName -like $name } | Where-Object { $_.0 Microsoft SQL Server Compact 3.8080.0.InstallDate. 'yyyyMMdd'.(New-TimeSpan -days $days) $cutoffstring = Get-Date -date $cutoff -format 'yyyyMMdd' $path = 'Registry::HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Unins tall\*' $column_days = @{ Name='Days' Expression={ if ($_.0657.DisplayName -ne $null } | Where-Object { $_.8107. If you don't.InstallDate) { (New-TimeSpan ([DateTime]::ParseExact($_. $days = 2000 ) $cutoff = (Get-Date) . you will get all software installed within the past 2. You do not have to submit them since they are optional.0 Microsoft Security Client DE-DE Language Pack 119 2. $column_Days.-------------Microsoft .0 Microsoft Antimalware Service DE-DE Language Pack 119 3.function Get-InstalledSoftware { param( $name = '*'. So when you run GetInstalledSoftware.0657.0.0.657. they are set to their default values.30319 Microsoft Antimalware 119 3.0 Microsoft Security Essentials 119 2.NET Framework 4 Client Profile 38 4.0.Days } else { 'n/a' } } } Get-ItemProperty -path $path | Where-Object { $_. Get-InstalledSoftware supports two optional parameters called -Name and -Days.000 days.0.InstallDate -gt $cutoffstring } | Select-Object DisplayName. $null))). DisplayVersion | Sort-Object DisplayName } Now.0 Adding Mandatory Parameters Let's assume you want to create a function that converts dollars to Euros . what happens when the user does not submit any parameter since -dollar is optional as well? Well. they are repeated.21 242 So. This function can only make sense if there was some information passed to $dollar. $rate=1. since you did not submit anything. the result looks strange because when you enter information via a prompt.param( $dollar.37 ) $dollar * $rate } This works because PowerShell will ask for it when you do not submit the -dollar parameter: PS > ConvertTo-Euro -rate 6. PowerShell will treat it as string (text) information. and when you multiply texts. which is why this parameter needs to be mandatory. $rate=1.37 ) $dollar * $rate } And here is how you run your new command: PS > ConvertTo-Euro -dollar 200 274 Since -rate is an optional parameter with a default value. you get back nothing. Here is how you declare it mandatory: function ConvertTo-Euro { param( [Parameter(Mandatory=$true)] $dollar.7 cmdlet ConvertTo-Euro at command pipeline position 1 Supply values for the following parameters: dollar: 100 100100100100100100100 However. there is no need for you to submit it unless you want to override the default value: PS > ConvertTo-Euro -dollar 200 -rate 1. So whenever you declare a parameter as . 00} equals EUR{1:0.mandatory. If you wanted to provide a way for your users to distinguish between raw and pretty output. and when it is not present. So. When present. you are taking the chance that the user will omit it and gets prompted for it. They are either present or not.37. it would be the raw numeric value: function ConvertTo-Euro { param( [Parameter(Mandatory=$true)] [Double] $dollar. $rate=1.00} at a rate of {2:0:0.37 ) $dollar * $rate } Now. $rate=1.7 cmdlet ConvertTo-Euro at command pipeline position 1 Supply values for the following parameters: dollar: 100 670 Adding Switch Parameters There is one parameter type that is special: switch parameters do not take arguments. you always need to make sure that you declare the target type you are expecting: function ConvertTo-Euro { param( [Parameter(Mandatory=$true)] [Double] $dollar. [switch] $pretty ) $result = $dollar * $rate if ($pretty) { '${0:0. $result. the function performs as expected: PS > ConvertTo-Euro -rate 6. You can assign the type [Switch] to that parameter to add switch parameters to your function. your currency converter could implement a switch parameter called -Pretty.00}' -f $dollar. the output would come as a nice text line. $rate } else { $result } . PARAMETER rate the exchange rate.28 -pretty $200. $rate=1. add a specially formatted comment block right before the function or at the beginning or end of the function script block: <# .EXAMPLE ConvertTo-Euro 100 -rate 2. .00 equals EUR256. $rate } else { $result .3 converts 100 dollars using a custom exchange rate #> function ConvertTo-Euro { param( [Parameter(Mandatory=$true)] [Double] $dollar.37.00} equals EUR{1:0.EXAMPLE ConvertTo-Euro 100 converts 100 dollars using the default exchange rate and positional parameters .00 at a rate of 1.PARAMETER dollar the dollar amount.28 256 PS > ConvertTo-Euro -dollar 200 -rate 1.DESCRIPTION Takes dollars and calculates the value in Euro by applying an exchange rate .00} at a rate of {2:0:0.37. . All you will need to do is add the Help text. This parameter is mandatory.00}' -f $dollar.} Now. The default value is set to 1. It can also return Help information for your self-defined functions.SYNOPSIS Converts Dollar to Euro . To do that.28 Adding Help to your Functions Get-Help returns Help information for all of your cmdlets. [switch] $pretty ) $result = $dollar * $rate if ($pretty) { '${0:0. $result. it is up to your user to decide which output to choose: PS > ConvertTo-Euro -dollar 200 -rate 1. type: "get-help ConvertTo-Euro -examples".EXAMPLE 2 -------------------------- .} } Note that the comment-based Help block may not be separated by more than one blank line if you place it above the function. for technical information. PS > Get-Help -name ConvertTo-Euro -Examples NAME ConvertTo-Euro SYNOPSIS Converts Dollar to Euro -------------------------. type: "get-help ConvertTo-Euro -full". type: "get-help ConvertTo-Euro -detailed". If you did everything right. you will now be able to get the same rich help like with cmdlets after running the code: PS > ConvertTo-Euro -? NAME ConvertTo-Euro SYNOPSIS Converts Dollar to Euro SYNTAX ConvertTo-Euro [-dollar] <Double> [[-rate] <Object>] [-pretty] [<CommonParameters>] DESCRIPTION Takes dollars and calculates the value in Euro by applying an exchange rate RELATED LINKS REMARKS To see the examples.EXAMPLE 1 -------------------------C:\PS>ConvertTo-Euro 100 converts 100 dollars using the default exchange rate and positional parameters -------------------------. for more information. Required? Position? Default value Accept pipeline input? Accept wildcard characters? -pretty [<SwitchParameter>] Required? Position? Default value Accept pipeline input? Accept wildcard characters? false named false false 2 false Creating Pipeline-Aware Functions Your function is not yet pipeline aware/ So.3 converts 100 dollars using a custom exchange rate PS > Get-Help -name ConvertTo-Euro -Parameter * -dollar <Double> the dollar amount. it will simply ignore the results delivered by the upstream cmdlet if you call it within a pipeline statement: 1.10 | ConvertTo-Euro Instead. you can fix it by choosing the parameter that is to receive the pipeline input. you will receive exceptions complaining about PowerShell not being able to "bind" the input object..C:\PS>ConvertTo-Euro 100 -rate 2. Here is the enhanced param block: . The default value is set to 1. This parameter is mandatory. If you want your function to be pipeline aware. That's because PowerShell cannot know which parameter is supposed to receive the incoming pipeline values.37. Required? Position? Default value Accept pipeline input? Accept wildcard characters? true 1 false -rate <Object> the exchange rate. $rate = 1. . By adding ValueFromPipeline=$true. [switch] $pretty ) . This parameter is mandatory.3 converts 100 dollars using a custom exchange rate #> function ConvertTo-Euro { param( [Parameter(Mandatory=$true.PARAMETER rate the exchange rate.SYNOPSIS Converts Dollar to Euro . though: PS > 1. Here is how you move the code into a process block to make a function process all incoming pipeline values: <# . ValueFromPipeline=$true)] [Double] $dollar. Filters will by default execute all code in a process block. If you want the code to process each incoming pipeline data.7 This is because functions will by default execute all code at the end of a pipeline. ValueFromPipeline=$true)] [Double] $dollar. The default value is set to 1. $rate=1. .37.PARAMETER dollar the dollar amount. you must assign the code manually to a process script block or rename your function into a filter (by exchanging the keyword function by filter)...function ConvertTo-Euro { param( [Parameter(Mandatory=$true. Your function will only process the last incoming result. . you are telling PowerShell that the parameter -dollar is to receive incoming pipeline input. there are no more exceptions.37.EXAMPLE ConvertTo-Euro 100 -rate 2.10 | ConvertTo-Euro 13.EXAMPLE ConvertTo-Euro 100 converts 100 dollars using the default exchange rate and positional parameters .37. When you rerun the script and then try the pipeline again.DESCRIPTION Takes dollars and calculates the value in Euro by applying an exchange rate .. This will get you a colored prompt: function prompt { Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Magenta " " } You can also insert information into the console screen buffer. and end.ui. such as PowerShell ISE.[switch] $pretty ) begin {"starting.00}' -f $dollar. process. $rate } else { $result } } end { "Done!" } } As you can see. The most important place for customization is the function prompt.Height . It is responsible for displaying the PowerShell prompt. $result. which is executed automatically once a command is done.rawui. function prompt { Write-Host ("PS " + $(get-location) +">") -nonewline -foregroundcolor Green " " $winHeight = $Host. no code will exist outside of any one of these three blocks anymore.. your function code is now assigned to one of three special script blocks: begin. Playing With Prompt Functions PowerShell already contains some pre-defined functions.WindowSize. You can enumerate the special drive function if you would like to see all available functions: Dir function: Many of these pre-defined functions perform important tasks in PowerShell.. You can change your PowerShell prompt by overriding the function prompt. This only works with true consoles so you cannot use this type of prompt in non-console editors. Once you add one of these blocks."} process { $result = $dollar * $rate if ($pretty) { '${0:0.00} at a rate of {2:0:0.00} equals EUR{1:0. which execute code whenever you enter the assigned name. Parameters can do pretty much anything that cmdlet parameters can do. optional. That's what distinguishes functions from aliases.ui. functions are called script blocks.WindowTitle = (Get-Location). you can provide the user with the option to submit additional information to your function code. "PS> " } And this prompt function changes colors based on your notebook battery status (provided you have a battery): function prompt { $charge = get-wmiobject Win32_Battery | Measure-Object -property EstimatedChargeRemaining -average | Select-Object -expandProperty Average if ($charge -lt 25) { $color = "Red" } elseif ($charge -lt 50) { $color = "Yellow" } else { $color = "White" } $prompttext = "PS {0} ({1}%)>" –f (get-location).ui.CursorPosition = $curPos } Another good place for additional information is the console window title bar.Y-=$winHeight $newPos.CursorPosition $newPos = $curPos $newPos. You can even add Switch parameters to your function. Here is a prompt that displays the current location in the title bar to save room inside the console and still display the current location: function prompt { $host.ui.X = 0 $newPos. An alias serves solely as a replacement for another command name.ui.rawui. $newPos. . or a special data type. As such.rawui. have a default value. PBy adding parameters. $charge Write-Host $prompttext -nonewline -foregroundcolor $color " " } Summary You can use functions to create your very own new cmdlets.Y = [Math]::Max(0. You will also need to move the function code into a process block so it gets executed for each incoming result.rawui.$curPos = $Host.Y+1) $Host. They can be mandatory.CursorPosition = $newPos Write-Host ("{0:D} {0:T}" -f (Get-Date)) -foregroundcolor Yellow $Host. a function can execute whatever code you want. If you want your function to work as part of a PowerShell pipeline. In its most basic form. you will need to declare the parameter that should accept pipeline input from upstream cmdlets.rawui. or commercial products like "PowerShell Plus". When you save the script with a generic text editor. pretty much like if you had entered the code manually into PowerShell.ps1 To save multiple lines to a script file using redirection.You can play with many more parameter attributes and declarations. So script files are a great way of automating complex tasks that consist of more than just one line of code. Scripts can also serve as a repository for functions you create. Topics Covered: Creating a Script o Launching a Script o Execution Policy . use "here-strings": .1: Execution policy setting options Invoking Scripts like Commands Parameters: Passing Arguments to Scripts o Scopes: Variable Visibility o Profile Scripts: Automatic Scripts o Signing Scripts with Digital Signatures o Finding Certificates o Creating/Loading a New Certificates Creating Self-Signed Certificates o Making a Certificate "Trustworthy" o Signing PowerShell Scripts o Checking Scripts o Table 10. It can set colors. A profile script is used to set up your personal PowerShell environment. All the code that you entered and tested interactively can also be stored in a script file. You can even set up a so called "profile" script which runs automatically each time you launch PowerShell. When you run the script file. make sure you add the file extension ". you could even create it directly from within the console by redirecting the script code to a file: ' "Hello world" ' > $env:temp\myscript. the code inside is executed from top to bottom. so whenever you run a script. You can place any PowerShell code inside your script. it defines all the functions you may need for your daily work. You can create it with any text editor or use specialized PowerShell editors like the built-in "Integrated Script Environment" called "ise". If your script is rather short. and load additional PowerShell modules and snapins.ps1".ps1". define the prompt.3: Status reports of signature validation and their causes o Summary Creating a Script A PowerShell script is a plain text file with the extension ". Try this to get a complete overview: Help advanced_parameter PowerShell can be used interactively and in batch mode.Allowing Scripts to Run Table 10. @' $cutoff = (Get-Date) - (New-Timespan -hours 24) $filename = "$env:temp\report.txt" Get-EventLog -LogName System -EntryType Error,Warning -After $cutoff | Format-Table -AutoSize | Out-File $filename -width 10000 Invoke-Item $filename '@ > $env:temp\myscript.ps1 Launching a Script To actually run your script, you need to either call the script from within an existing PowerShell window, or prepend the path with "powershell.exe". So, to run the script from within PowerShell, use this: & "$env:temp\myscript.ps1" By prepending the call with "&", you tell PowerShell to run the script in isolation mode. The script runs in its own scope, and all variables and functions defined by the script will be automatically discarded again once the script is done. So this is the perfect way to launch a "job" script that is supposed to just "do something" without polluting your PowerShell environment with left-overs. By prepending the call with ".", which is called "dot-sourcing", you tell PowerShell to run the script in global mode. The script now shares the scope with the callers' scope, and functions and variables defined by the script will still be available once the script is done. Use dot-sourcing if you want to debug a script (and for example examine variables), or if the script is a function library and you want to use the functions defined by the script later. To run a PowerShell script from outside PowerShell, for example from a batch file, use this line: Powershell.exe -noprofile -executionpolicy Bypass -file %TEMP%\myscript.ps1 You can use this line within PowerShell as well. Since it always starts a fresh new PowerShell environment, it is a safe way of running a script in a default environment, eliminating interferences with settings and predefined or changed variables and functions. Execution Policy - Allowing Scripts to Run If you launched your script from outside PowerShell, using an explicit call to powershell.exe, your scripts always ran (unless you mistyped something). That's because here, you submitted the parameter executionpolicy and turned the restriction off for the particular call. To enable PowerShell scripts, you need to change the ExecutionPolicy. There are actually five different execution policies which you can list with this command: PS > Get-ExecutionPolicy -List Scope ExecutionPolicy ------------------ MachinePolicy Undefined UserPolicy Undefined process Undefined CurrentUser Bypass LocalMachine Unrestricted The first two represent group policy settings. They are set to "Undefined" unless you defined ExecutionPolicy with centrally managed group policies in which case they cannot be changed manually. Scope "Process" refers to the current PowerShell session only, so once you close PowerShell, this setting gets lost. CurrentUser represents your own user account and applies only to you. LocalMachine applies to all users on your machine, so to change this setting you need local administrator privileges. The effective execution policy is the first one from top to bottom that is not set to "Undefined". You can view the effective execution policy like this: PS > Get-ExecutionPolicy Bypass If all execution policies are "Undefined", the effective execution policy is set to "Restricted". Setting Restricted Default Description Script execution is absolutely prohibited. Standard system setting normally corresponding to "Restricted". Only scripts having valid digital signatures may be executed. Signatures ensure that the AllSigned script comes from a trusted source and has not been altered. You'll read more about signatures later on. Scripts downloaded from the Internet or from some other "public" location must be signed. Locally stored scripts may be executed even if they aren't signed. Whether a script is RemoteSigned "remote" or "local" is determined by a feature called Zone Identifier, depending on whether your mail client or Internet browser correctly marks the zone. Moreover, it will work only if downloaded scripts are stored on drives formatted with the NTFS file system. Unrestricted PowerShell will execute any script. Table 10.1: Execution policy setting options Many sources recommend changing the execution policy to "RemoteSigned" to allow scripts. This setting will protect you from potentially harmful scripts downloaded from the internet while at the same time, local scripts run fine. The mechanism behind the execution policy is just an additional safety net for you. If you feel confident that you won't launch malicious PowerShell code because you carefully check script content before you run scripts, then it is ok to turn off this safety net altogether by setting the execution policy to "Bypass". This setting may be required in some corporate scenarios where scripts are run off file servers that may not be part of your own domain. If you must ensure maximum security, you can also set execution policy to "AllSigned". Now, every single script needs to carry a valid digital signature, and if a script was manipulated, PowerShell immediately refuses to run it. Be aware that this setting does require you to be familiar with digital signatures and imposes considerable overhead because it requires you to re-sign any script once you made changes. Invoking Scripts like Commands To actually invoke scripts just as easily as normal commands—without having to specify relative or absolute paths and the ".ps1" file extension—pick or create a folder to store your scripts in. Next, add this folder to your "Path" environment variable. Done. md $env:appdata\PSScripts copy-item $env:temp\myscript.ps1 $env:appdata\PSScripts\myscript.ps1 $env:path += ";$env:appdata\PSScripts " myscript The changes you made to the "Path" environment variable are temporary and only valid in your current PowerShell session. To permanently add a folder to that variable, make sure you append the "Path" environment variable within your special profile script. Since this script runs automatically each time PowerShell starts, each PowerShell session automatically adds your folder to the search path. You learn more about profile scripts in a moment. Parameters: Passing Arguments to Scripts Scripts can receive additional arguments from the caller in much the same way as functions do (see Chapter 9). Simply add the param() block defining the parameters to the top of your script. You learned about param() blocks in the previous chapter. For example, to add parameters to your event log monitoring script, try this: @' Param( $hours = 24, [Switch] $show ) $cutoff = (Get-Date) - (New-Timespan -hours $hours) $filename = "$env:temp\report.txt" Get-EventLog -LogName System -EntryType Error,Warning -After $cutoff | Format-Table -AutoSize | Out-File $filename -width 10000 If ($Show) { Invoke-Item $filename } else { Write-Warning "The report has been generated here: $filename" } '@ > $env:temp\myscript.ps1 Now you can run your script and control its behavior by using its parameters. If you copied the script to the folder that you added to your "Path" environment variable, you can even call your script without a path name, almost like a new command: PS > copy-item $env:temp\myscript.ps1 $env:appdata\PSScripts\myscript.ps1 PS > myscript -hours 300 WARNING: The report has been generated here: C:\Users\w7-pc9\AppData\Local\Temp\report.txt PS > myscript -hours 300 -show To learn more about parameters, how to make them mandatory or how to add help to your script, refer to the previous chapter. Functions and scripts share the same mechanism. Scopes: Variable Visibility Any variable or function you define in a script by default is scoped "local". The variable or function is visible from subscopes (like functions or nested functions or scripts called from your script). It is not visible from superscopes (like the one calling the script) unless the script was called dot-sourced. So by default, any function or variable you define can be accessed from any other function defined at the same scope or in a subscope: function A { "Here is A" } function B { "Here is B" } function C { A; B } C The caller of this script cannot access any function or variable, so the script will not pollute the callers context with left-over functions or variables - unless you call the script dot-sourced like described earlier in this chapter. By prefixing variables or function names with one of the following prefixes, you can change the default behavior. Script: use this for "shared" variables. Global: use this to define variables or functions in the callers' context so they stay visible even after the script finished Private: use this to define variables or functions that only exist in the current scope and are invisible to both super- and subscopes. Profile Scripts: Automatic Scripts Most changes and adjustments you make to PowerShell are only temporary, and once you close and reopen PowerShell, they are lost. To make changes and adjustments persistent, use profile scripts. These scripts get executed automatically whenever PowerShell starts (unless you specify the -noprofile paramater). The most widely used profile script is your personal profile script for the current PowerShell host. You find its path in $profile: PS > $profile C:\Users\w7pc9\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 Since this profile script is specific to your current PowerShell host, the path may look different depending on your host. When you run this command from inside the ISE editor, it looks like this: PS > $profile C:\Users\w7pc9\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1 If this file exists, PowerShell runs it automatically. To test whether the script exists, use Test-Path. Here is a little piece of code that creates the profile file if it not yet exists and opens it in notepad so you can add code to it: PS > if (!(Test-Path $profile)) { New-Item $profile -Type File -Force | Out-Null }; notepad $profile There are more profile scripts. $profile.CurrentUserAllHosts returns the path to the script file that automatically runs with all PowerShell hosts, so this is the file to place code in that should execute regardless of the host you use. It executes for both the PowerShell console and the ISE editor. $profile.AllUsersCurrentHost is specific to your current host but runs for all users. To create or change this file, you need local administrator privileges. $profile.AllUsersAllHosts runs for all users on all PowerShell hosts. Again, you need local administrator privileges to create or change this file. If you use more than one profile script, their execution order is from "general to specific", so the profile script defined in $profile executes last (and if there are conflicting settings, overrides all others). Signing Scripts with Digital Signatures To guarantee that a script comes from a safe source and wasn't manipulated, scripts can be signed with a digital signature. This signature requires a so called "Codesigning Certificate" which is a digital certificate with a private key and the explicit purpose of validating code. You can get such a certificate from your corporate IT (if they run a PKI infrastructure), or you can buy it from certificate authorities like Verisign or Thawte. You can even create your own "self-signed" certificates which are the least trustworthy alternative. Finding Certificates To find all codesigning certificates installed in your personal certificate store, use the virtual cert: drive: Dir cert:\Currentuser\My -codeSigningCert directory: Microsoft.PowerShell.Security\Certificate::CurrentUser\My Thumbprint ---------E24D967BE9519595D7D1AC527B6449455F949C77 Subject ------CN=PowerShellTestCert The -codeSigningCert parameter ensures that only those certificates are located that are approved for the intended "code signing" purpose and for which you have a private and secret key. If you have a choice of several certificates, pick the certificate you want to use for signing by using WhereObject: $certificate = Dir cert:\CurrentUser\My | Where-Object { $_.Subject -eq "CN=PowerShellTestCert" } You can also use low-level -NET methods to open a full-featured selection dialog to pick a certificate: $Store = New-Object system.security.cryptography.X509Certificates.x509Store("My", "CurrentUser") $store.Open("ReadOnly") [System.Reflection.Assembly]::LoadWithPartialName("System.Security") $certificate = [System.Security.Cryptography.x509Certificates.X509Certificate2UI]::Sel ectFromCollection($store.certificates, "Your certificates", "Please select", 0) $store.Close() $certificate Thumbprint Subject ---------------372883FA3B386F72BCE5F475180CE938CE1B8674 CN=MyCertificate Creating/Loading a New Certificate If there is no certificate in your certificate store, you cannot sign scripts. You can then either request/purchase a codesigning certificate and install it into your personal certificate store by doubleclicking it, or you can temporarily load a certificate file into memory using Get-PfxCertificate. Creating Self-Signed Certificates The key to making self-signed certificates is the Microsoft tool makecert.exe. Unfortunately, this tool can't be downloaded separately and it may not be spread widely. You have to download it as part of a free "Software Development Kit" (SDK). Makecert.exe is in the .NET framework SDK which you can find at http://msdn2.microsoft.com/en-us/netframework/aa731542.aspx. After the SDK is installed, you'll find makecert.exe on your computer and be able to issue a new codesigning certificate with a name you specify by typing the following lines: $name = "PowerShellTestCert" pushd Cd "$env:programfiles\Microsoft Visual Studio 8\SDK\v2.0\Bin" .\makecert.exe -pe -r -n "CN=$name" -eku 1.3.6.1.5.5.7.3.3 -ss "my" popd It will be automatically saved to the \CurrentUser\My certificate store.msc Self-signed certificates are not trustworthy by default because anyone can create them. so you may not be able to sign scripts created with ISE unless you save the scripts with a different encoding. That's because their root is listed in the "trusted root certification authorities container.ps1 -recurse -erroraction SilentlyContinue | Set-AuthenticodeSignature -cert $certificate When you look at the signed scripts. use GetAuthenticodeSignature: . use this approach: Dir C:\ -filter *. You can examine these settings like this: Certmgr. to sign all PowerShell scripts on a drive. The following code grabs the first available codesigning certificate and then signs a script: $certificate = @(Dir cert:CurrentUser\My -codeSigningCert -recurse)[0] Set-AuthenticodeSignature c:\scripts\test. you'll see a new comment block at the end of a script. you need to copy them into the list of trusted root certification authorities and Trusted Publishers. Unfortunately. or that are saved with Big Endian Unicode. Signing PowerShell Scripts PowerShell script signatures require only two things: a valid code-signing certificate and the script that you want to sign. Checking Scripts To check all of your scripts manually and find out whether someone has tampered with them.Subject -eq "CN=$name"} Making a Certificate "Trustworthy" Certificates you purchased from trusted certificate authorities or your own enterprise IT are considered trustworthy by default. the builtin script editor ISE uses just that encoding scheme to save scripts.ps1 $certificate Likewise. From this location. The cmdlet Set-AuthenticodeSignature takes care of the rest. Attention: You cannot sign script files that are smaller than 4 Bytes. To make them trustworthy. you can now call and use any other certificate: $name = "PowerShellTestCert" $certificate = Dir cert:\CurrentUser\My | Where-Object { $_. All variables and functions defined in the script will remain intact even once the script finished. signature and the signature is valid. The script will signature. 'UnknownError' -contains $_. resign the file. They work like batch files and may include any PowerShell statements. but ended in a root certificate which is not trusted root certificates authorities trusted by the trust provider. The used certificate is unknown. By default. file. you must use SetNotSigned not execute on the system. use Where-Object to filter your results: dir c:\ -filter *. whose contents have been tampered with since they were signed (HashMismatch). Table 10. The script will not execute on the system. To run scripts from outside PowerShell. The file contents match the Valid Signature was validated. signature. or whose signature comes from an untrusted certificate (UnknownError).3: Status reports of signature validation and their causes Summary PowerShell scripts are plain text files with a ".Dir C:\ -filter *.ps1 -recurse -erroraction silentlycontinue | GetAuthenticodeSignature | Where-Object { 'HashMismatch'. the execution policy disables all PowerShell scripts. File XXX check this cannot be loaded. There are additional parameters like -noprofile which ensures that the script runs in a default powershell environment that was not changed by profile scripts. This can be useful for debugging scripts. 'NotSigned'. Please see "get-help about_signing" for more details. By running a script "dot-sourced" (prepending the path by a dot and a space).ps1 -recurse -erroraction SilentlyContinue | GetAuthenticodeSignature If you want to find only scripts that are potentially malicious. The file "xyz" cannot be loaded. store.ps1" file extension.exe and specify the script path. Please see "get-help AuthenticodeSignature to sign the about_signing" for more details. The contents of file "…" may have been tampered because the hash The file contents were changed. You can run a script from within PowerShell: specify the absolute or relative path name to the script unless the script file is stored in a folder that is part of the "Path" environment variable in which case it is sufficient to specify the script file name. and it is essential for running "library" scripts that define functions you want to use elsewhere. A certificate chain Add the certificate publisher to the UnknownError processed. the script runs in the callers' context. call powershell. If HashMismatch of the file does not match the hash stored in the digital you changed the contents yourself. To run a script. . you need to make sure the execution policy setting is allowing the script to execute.Status } Status Message Description Since the file has no digital The file "xyz" is not digitally signed. The default ErrorAction is "Continue": the cmdlet outputs errors but continues to run. you can suppress these errors to get at least those files that you are able to access: Get-Process -FileVersion -ErrorAction SilentlyContinue Suppress errors with care because errors have a purpose. you learn how to discover and handle runtime errors gracefully. So only suppress errors you know are benign. When you design a PowerShell script. For example. If you just want the files you can get your hands on and suppress ugly error messages. try this: PS> Get-Childitem $env:windir -ErrorAction SilentlyContinue -recurse -filter *. if you do not have full local administrator privileges. when you search the windows folder recursively for some files or folder. it is invaluable to receive errors. In many situations. and suppressing errors will not solve the underlying problem.log Likewise. In this chapter. The default ErrorAction applies to all cmdlets that do not specify an individual ErrorAction by using the parameter -ErrorAction. By default.Digital signatures ensure that a script comes from a trusted source and has not been tampered with. . To suppress error messages. set the ErrorAction to SilentlyContinue. there may be situations where you cannot eliminate all possible runtime errors. Topics Covered: Suppressing Errors Handling Errors o Try/Catch o Using Traps Handling Native Commands o Understanding Exceptions o Handling Particular Exceptions o Throwing Your Own Exceptions o Stepping And Tracing Summary Suppressing Errors Every cmdlet has built-in error handling which is controlled by the -ErrorAction parameter. This default is controlled by the variable $ErrorActionPreference. you cannot access processes you did not start yourself. the remote machine may not be available. Listing process files would produce a lot of error messages. If your script maps network drives. PowerShell would then throw an exception but would continue to search through the subfolders. there could be a situation where no more drive letters are available. get alarmed and act accordingly. When you assign a different setting to this variable. You can sign scripts and also verify a script signature with Set-AuthenticodeSignature and GetAuthenticodeSignature. your code may eventually touch system folders where you have no sufficient access privileges. Again. it becomes the new default ErrorAction. and when your script performs a remote WMI query. your code needs to set up an error handler to become aware of errors. that is considered not a terminating error. you could add the parameter -ErrorAction Stop to individual cmdlet calls but chances are you would not want to do this for every single call except if you wanted to handle only selected cmdlets errors.1'. To handle errors. They always come as pair and need to follow each other immediately.0. Changing the default ErrorAction is much easier in most situations. There is a local error handler (try/catch) and also a global error handler (trap). the error will still appear. errors will not get suppressed despite using SilentlyContinue. Get-WMIObject will throw a (non-maskable) terminating error when you use -ComputerName to access a remote computer and receive an "Access Denied" error. the most important step is setting the ErrorAction default to Stop: $ErrorActionPreference = 'Stop' As an alternative. If a cmdlet encounters a serious error (which is called "Terminating Error").0. Once you changed the ErrorAction to Stop. 'offline' | ForEach-Object { try { Get-WmiObject -class Win32_BIOS -computername $_ -ErrorAction Stop | Select-Object __Server. It then can take steps to respond to that error. It then uses Foreach-Object to feed the computer names into Get-WMIObject which remotely tries to get BIOS information from these machines. Whether or not an error is considered "serious" or "terminating" is solely at the cmdlet authors discretion. If Get-WMIObject encounters an "RPC system not available" error because the machine you wanted to access is not online. 'storage1'. The try-block marks the area of your code where you want to handle errors. Version } catch { Write-Warning "Error occured: $_" } } It takes a list of computer names (or IP addresses) which could also come from a text file (use Get-Content to read a text file instead of listing hard-coded computer names). Handling Errors To handle an error. The catch-block defines the code that is executed when an error in the try-block occurs. so this type of error would be successfully suppressed.NOTE: Sometimes. '127. use the try/catch statements. The ErrorAction setting not only affects cmdlets (which have a parameter -ErrorAction) but also native commands (which do not have such a parameter and thus can only be controlled via the default setting). Take a look at this simple example: 'localhost'. For example. Try/Catch To handle errors in selected areas of your code. your code needs to become aware that there was an error. and the cmdlet will stop and not continue regardless of your ErrorAction setting. . You can mix both if you want. 'nonexistent'. Second. (Exception from HRESULT: 0x800706BA) in "C:\Users\w7pc9\AppData\Local\Temp\Untitled3. So it can no longer be used to read the current computer name.Get-WMIObject is encapsulated in a try-block and also uses the ErrorAction setting Stop. Inside the catch-block. So when an error was handled.0. To examine the object stored in $_. the current computer name processed by Foreach-Object needed to be stored in a new variable because the standard $_ variable is reused inside the catch-block and refers to the current error. This variable contains a complex object which contains all details about the error.1'. This is how you would adjust the catch-block: . This way. so any error this cmdlet throws will execute the catch-block. The reason for the error is available in $_ inside the catch-block. Version } catch { Write-Warning ('Failed to access "{0}" : {1} in "{2}"' -f $currentcomputer. inside the catch-block. 'nonexistent'. This is why only GetWMIObject is placed inside the try-block. $_. the warning is a lot more explicit: WARNING: Failed to access "nonexistent" : The RPC server is unavailable. Note also that in this example. in this example a warning is outputted. you can examine your test variable using Get-Member. not the Foreach-Object statement. (Exception from HRESULT:0x800706BA) You may want to report the name of the script where the error occured. 'storage1'. When you remove the -ErrorAction parameter from Get-WMIObject. Try and play with this example. 'offline' | ForEach-Object { try { $ErrorActionPreference = 'Stop' $currentcomputer = $_ Get-WmiObject -class Win32_BIOS -computername $currentcomputer Select-Object __Server. and of course you'd want to output the computer name that failed. '127. the loop continues to run and continues to process the remaining computers in your list.Message.InvocationInfo. So when an error does occur. you can save it in a global variable. The error message created by the catch-block is not yet detailed enough: WARNING: Error occured: The RPC server is unavailable.ps1" Here. That's why the example stored the content of $_ in $currentcomputer before an error could occur. ` $_. two procedures were needed: first of all.Exception. Here is a slight variant which accomplishes these tasks. This way. the script code became more legible as well. the general ErrorActionPreference was set to Stop so it no longer is necessary to submit the -ErrorAction parameter to individual cmdlets: 'localhost'. Information about the cause can be found in the property Exception whereas information about the place the error occured are found in InvocationInfo.ScriptName) } } | This time. PowerShell jumps to the corresponding catch-block and will not return and resume the try-block. the object remains accessible (else it would be discarded once the catch-block is processed). Also note that whenever an error occurs in the try-block. you will notice that errors will no longer be handled.0. $_ resembles the current error. Automation.. Exception Property System..Management.} InvocationInfo Property System.Management.Runtime. the error information has a number of subproperties like the one used in the example.Seriali. $_.Dictionary`2[[System.Exception.Automation.Automation.. m..ScriptName) } } Then.Object PSMessageDetails {get=& { Set-Stri.InvocationInfo TypeName: System..Collections.ErrorCategoryInfo C..Automation.InvocationInfo Invo.Management...Generic. ` $_.Collections. ErrorDetails Property System.} FullyQualifiedErrorId Property System..} PSMessageDetails ScriptProperty System.Management.Automation.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() BoundParameters Property System.Object TargetObject {get.Void GetObjectData(System. One of the more useful properties is InvocationInfo which you can examine like this: PS> Get-Member -InputObject $test.. PipelineIterationInfo Property System.ReadOnlyCollectio. .ObjectModel.Exception Exception {get.Object obj) GetHashCode Method int GetHashCode() GetObjectData Method System..String FullyQualifiedErrorId {get.InvocationInfo. TargetObject Property System. check the content of $test: PS> Get-Member -InputObject $test TypeName: System.String.catch { $global:test = $_ Write-Warning ('Failed to access "{0}" : {1} in "{2}"' -f $currentcomputer.ErrorRecord Name MemberType Definition ---------------------Equals Method bool Equals(System..Management.Message.. As you see..---------Equals Method bool Equals(System.InvocationInfo Name MemberType Definition ------------.ErrorDetails ErrorD. once the script ran (and encountered an error). GetType Method type GetType() ToString Method string ToString() CategoryInfo Property System. it continues execution with the next statement following the erroneous statement . ExpectingInput Property HistoryId Property InvocationName Property Line Property MyCommand Property MyCommand {get.String ScriptName {get.} System.. you would get your own error message and then also the official PowerShell error message.} System. Whenever your code uses braces.} System.} OffsetInLine Property PipelineLength Property PipelinePosition Property PositionMessage Property ScriptLineNumber Property ScriptName Property UnboundArguments Property mscorli. It does contain one more statement to make it act like a catch-block: Continue.1'. the trap handles the error just fine. $_. though.Int32 OffsetInLine {get. Actually.} System. When the first error occurs.Message. When you run this script.CommandInfo System. 'offline' | ForEach-Object { $currentcomputer = $_ Get-WmiObject -class Win32_BIOS -computername $currentcomputer ErrorAction Stop | Select-Object __Server.Management.0. Check out this example: trap { Write-Warning ('Failed to access "{0}" : {1} in "{2}"' -f $currentcomputer. 'nonexistent'.Exception. So when you look at the example code.in the scope of the handler.Management.Automation.0.Collections. Without using Continue.String InvocationName {get.} System.ScriptName) continue } 'localhost'. System.InvocationInfo.String PositionMessage {get.} System.. It does not execute the remaining computers in your list.List`1[[System. Why? Whenever an error occurs and your handler gets executed.Int32 PipelineLength {get. It tells you all details about the place the error occured.CommandOrigin Property CommandOrigin . '127. So without Continue.Object. a trap really is almost like a catch-block without a try-block.Int32 PipelinePosition {get.. the code inside the braces . 'storage1'.String Line {get. you can also use a global error handler which is called "Trap". you'll notice that the error occurred inside the Foreach-Object loop. you will notice differences.} System.Int64 HistoryId {get. the trap would handle the error but then forward it on to other handlers including PowerShell.Generic.} System.Int32 ScriptLineNumber {get. the script uses a trap at its top which looks almost like the catch-block used before. ` $_.} System.CommandOrigin System. but then the script stops.Automation.Boolean ExpectingInput {get.} System. Using Traps If you do not want to focus your error handler on a specific part of your code.. Version } This time. So the trap did process the first error correctly and then continued with the next statement in its own scope.InvocationInfo. but the error is not red nor does it look like the usual PowerShell error messages because it comes as plain text directly from the application you ran: PS> net user willibald The user name could not be found. and since they do not necessarily use the . Since these commands were not developed specifically for PowerShell. At line:1 char:4 . '127.Exception. More help is available by typing NET HELPMSG 2221.0. PowerShell can monitor this channel and treat outputs that come from this channel as regular exceptions. and also you can change the position of your trap. you need to do two things: first of all. The only command type that does not fit into this error handling scheme are native commands. Here is an example: When you run the following native command. you need to set $ErrorActionPreference to Stop. You can choose between try/catch and trap. and second. Since there was no code following your loop. To make this work. you will receive an error. If you placed your trap inside the "territory" or "scope" where the error occurs.resembles a new "territory" or "scope". nothing else was executed.NET framework. ` $_. $_.ScriptName) continue } $currentcomputer = $_ Get-WmiObject -class Win32_BIOS -computername $currentcomputer ErrorAction Stop | Select-Object __Server.1'. Version } Handling Native Commands Most errors in your PowerShell code can be handled in the way described above. This example illustrates that it always is a good idea to plan what you want your error handler to do. you need to redirect the ErrOut channel to the StdOut channel because only this channel is processed by PowerShell. you could make sure all computers in your list are processed: 'localhost'. they cannot directly participate in PowerShells error handling. 'storage1'. the error suddenly becomes red and is turned into a "real" PowerShell error: PS> net user willibald 2>&1 net.0.exe : The user name could not be found. Console-based applications return their error messages through another mechanism: they emit error messages using the console ErrOut channel.Message. 'nonexistent'. 'offline' | ForEach-Object { trap { Write-Warning ('Failed to access "{0}" : {1} in "{2}"' -f $currentcomputer. When you redirect the error channel to the output channel. the catch-block never executes: try { net user willibald 2>&1 } catch { Write-Warning "Oops: $_" } As you know from cmdlets. this was easy because each cmdlet has a -ErrorAction preference. and the bubble bubbles up to the surface.PrivateData. In this chapter. This is why you need to use $ErrorActionPreference to set the ErrorAction to Stop: try { $ErrorActionPreference = 'Stop' net user willibald 2>&1 } catch { Write-Warning "Oops: $_" } If you do not like the default colors PowerShell uses for error messages. Native commands do not have such a parameter.PrivateData. and you got the chance to replace the default error message with your own or take appropriate action to handle the error. to handle errors you need to set the ErrorAction to Stop. . When you place the code in a try/catch-block. you learned how you can catch the bubble before it reaches the surface. Understanding Exceptions Exceptions work like bubbles in a fish tank.+ net <<<< user willibald 2>&1 + CategoryInfo : NotSpecified: (The user name could not be found.ErrorBackgroundColor = "White" You can also find additional properties in the same location which enable you to change the colors of warning and debugging messages (like WarningForegroundColor and WarningBackgroundColor). RemoteException + FullyQualifiedErrorId : NativeCommandError More help is available by typing NET HELPMSG 2221. it burps. PowerShell notices the bubble and throws the exception: it outputs a red error message. Whenever a fish gets sick. You can still not handle the error.:String) [].ErrorForegroundColor = "Red" $Host. If it reaches the surface. so PowerShell would never notice the bubble. With cmdlets. simply change them: $Host. use a warning or write error information to a log file. Each pair of braces resembles own "territory" or "scope". you are free to output error information any way you want. continue } 1/$null Dir -MacGuffin } Test Divided by null! Incorrect parameter! Throwing Your Own Exceptions If you develop functions or scripts and handle errors. write several Trap (or Catch) statements and specify for each the type of exception it should handle: function Test { trap [System.DivideByZeroException] { "Divided by null!". all upstream scopes have a chance to catch and handle the exception or even replace it with another exception. function TextOutput([string]$text) { if ($text -eq "") { Throw "You must enter some text.Automation. continue } trap [System. That's why you can also throw your own exceptions.ParameterBindingException] { "Incorrect parameter!". you take away the opportunity for the caller to respond to errors . If you'd prefer to use one or several groups of different error handlers. and when a scope emits an exception (a "bubble").because the caller has no longer a way of detecting the error.The level the fish swims in the fish tank resembles your code hierarchy. With any of these. You could output it as plain text." } else { "OUTPUT: $text" } } # An error message will be thrown if no text is entered: TextOutput You have to enter some text. They can be caught by the caller using a trap or a try/catch-block.Management. At line:5 char:10 + Throw <<<< "You have to enter some text. Handling Particular Exceptions The code set by Trap is by default executed for any (visible) exception. This way you can create complex escalation scenarios." # No error will be output in text output: TextOutput Hello . 08.8}". $_.LastWriteTime..LastWriteTime. "") DEBUG: 2+ [String]::Format <<<< ("{0.---d---30. If you invoke a function or a script. an error: $_" } Oh. To enable tracing.OUTPUT: Hello The caller can now handle the error your function emitted and choose by himself how he would like to respond to it: PS> try { TextOutput } catch { "Oh.PowerShell.) Simple tracing will show you only PowerShell statements executed in the current context. If you would like to see the code. use this statement: Set-PSDebug -step .10} {1.8}".ToString("t")) Directory: C:\Users\w7-pc9 Mode LastWriteTime Length Name --------------------.ToString("d").. Set-PSDebug -trace 2 If you would like to turn off tracing again. only the invocation will be shown but not the code of the function or script. select 0: Set-PSDebug -trace 0 To step code. $_. PowerShell has also built-in methods to step code or trace execution.Core\FileSystem::".LastWriteTime. this is an important diagnostic feature to debug code.11. $_. However. In larger scripts.ToString("t")) d-r-04. turn on detailed traced by using the -trace 2 parameter.LastWriteTime. $_. Stepping And Tracing Commercial PowerShell development environments like PowerShellPlus from Idera make it easy for you to set breakpoints and step through code to see what it actually does.10} {1. an error: You must enter some text.PSParentPath. use this: PS> Set-PSDebug -trace 1 PS> dir DEBUG: 1+ <<<< dir DEBUG: 1+ $_.2010 06:36 Contacts (.2009 12:54 Application data DEBUG: 2+ [String]::Format <<<< ("{0.ToString("d").Replace <<<< ("Microsoft. Anything you define in PowerShell . you should enable strict mode like this: Set-StrictMode -Version Latest This will throw exceptions for unknown variables (possible typos). To catch errors from console-based native commands. Summary To handle errors in your code. you will end up in a nested prompt. execution of the code will continue. Else. they expire and are automatically removed from memory. Only then will cmdlets and native commands place errors in your control. As soon as you enter Exit. Just select the "A" operation for "Yes to All" in order to turn off the stepping mode.Now. To detect and respond to errors. It ensures that unknown variables will throw an error. Without the Strict option. On machines where you develop PowerShell code. suspend or abort. Anyway Working with Scopes o Accessing Variables in Other Scopes o Keeping Information Private o Using Private Scopes . make sure you set the ErrorAction to Stop. Topics Covered: What's a Scope. At the end of this chapter. Understanding and correctly managing scope can be very important. we will also be looking at how PowerShell finds commands and how to manage and control commands if there are ambiguous command names. The code will then be interrupted so you could analyze the system in the console or check variable contents. make sure to also call Continue at the end of your error handler to tell PowerShell that you handled the error. which you will recognize by the "<<" sign at the prompt. With trap. it will ask you for each statement whether you want to continue. redirect their ErrOut channel to StdOut. PowerShell then automatically converts the custom error emitted by the command into a PowerShell exception. nonexistent object properties and wrong cmdlet call syntax. Or you want certain PowerShell settings to apply only within a script. This chapter talks about "scope" and how you manage the life span of objects or scripts.have a certain life span. Eventually. when you execute PowerShell code. Maybe you are also wondering just why functions defined in a script you run won't show up in your PowerShell console. functions. Set-PSDebug has another important parameter called -strict.EnterNestedPrompt() inside a script or a function. If you choose Suspend by pressing "H". Tip: You can create simple breakpoints by using nested prompts: call $host.variables. it would still bubble up to PowerShell and cause the default error messages. PowerShell will simply set a null value for unknown variables. You want to make sure that a production script is not negatively influenced by "left-overs" from a previous script. or settings . use either a local try/catch-block (to catch errors in specific regions of your code) or trap (to catch all errors in the current scope). These questions all touch "scope". Note that the default behavior can be changed both by the user and the programmer. To create "shared" variables that are accessible to all functions. Whenever PowerShell tries to access a variable or function. "Inheritance" is the wrong term. Anything you define in that scope persists until you close PowerShell. Will the variable be accessible from within the script or function? Yes. Again. When you define something in one territory. Will this variable be available in child scopes. you would manually change scope. it first looks in the current scope. You'll learn about that in a minute. Accessing Variables in Other Scopes When you define a variable in your PowerShell console. this works almost like "inheritance". though. Script Block: Since functions really are named script blocks. This guarantees that functions won't interfere with each other and write to the same variables . this script by default runs in its own scope. Function: Every function runs yet in another scope. what has been said about functions also applies to script blocks. anything you define in a scope is visible to all child scopes. Script: When you run a PowerShell script. so variables and functions declared in a function are by default not visible to the outside. In a nutshell. there is no guarantee that you access a specific variable in a specific scope. it really works different. you learned that it is stored in the global scope which is parent to all other scopes. Let's assume you created a variable $a in the PowerShell console. enabling the script to store variables or functions in the callers' scope.always opens the first scope which is called "global".the PowerShell console or a development environment like ISE . Working with Scopes One important feature of PowerShell's implementation of scopes is the way how objects from one scope or territory are passed to child scopes. Let's check this out by looking at some real world examples. Although it looks a bit like "inheritance". unless a variable is declared in the current scope. When you now call a script. that'll be discussed in a minute. If it is not found there. too? Let's say you are calling a script or a function. another territory may not see the object. what you get will always be the variable or function that was declared in closest possible proximity to your current scope or territory. . though. You could also call it "territory". So. There are important default territories or scopes in PowerShell: PowerShell Session: Your PowerShell session .unless that is what you want. By default. because in PowerShell this works more like a "cross-scope traversal". PowerShell traverses the parent scopes and continues its search until it finds the object or ultimately reaches the global scope. By default. This ensures that a script will not leave behind left-overs that may influence the global scope or other scripts that you run later. it will. Anyway "Scope" represents the area a given object is visible in. so by default child scopes can access objects created in parent scopes. So any variables or functions a script declares will automatically be cleared again when the script ends. o Calling Scripts "Dot-Sourced" Managing Command Types o Invoking a Specific Command Type Summary What's a Scope. They run in their own scope or territory too. the scope is discarded. In reality. So the function is not at all doing what it was supposed to do. All of the unexpected behaviors can be explained with scopes. always declare variables and give them an initial value. PowerShell starts to search for the variable in the parent scopes. automatic type casting takes place: the content of the variable is automatically converted to a boolean value. Check it out. Here. the if statement checks to see whether $hasrun is equal to $true. Here is the revised function: function Test { if ($global:hasrun -eq $true) { 'This function was called before' } else { $global:hasrun = $true 'This function runs for the first time' . Once the function is done. it suddenly reports that it ran before. Anything except $null will result in $true. too. So why then does the function report that it has been called before once you define a variable $hasrun with arbitrary content in the console? When the function runs. in the PowerShell console enter this line: $hasrun = 'some value' When you now run the function Test again. you get the scripts' version of $a. and you can create and access global variables programmatically. it should notice that it was called before. And since the if statement compares a boolean value with the variable content. it finds the variable. If the script has not defined $a. So here is the first golden rule that derives from this: in your scripts and functions. though. and assign $null to the variable. it will state that it was called for the first time. you may get unexpected results. two things can happen: if your script has defined $a itself. you have to use global variables. Here is a sample: function Test { if ($true -eq $hasrun) { 'This function was called before' } else { $hasrun = $true 'This function runs for the first time' } } When you call the function Test for the first time. That's why the variable $hasrun cannot be used to remember a previous function call.and the script accesses the variable $a. A global variable basically is what you created manually in the PowerShell console. Each time the function runs. If you don't. you get the variable from the global scope that you defined in the console. then call the function again: PS> $hasrun = $null PS> test This function runs for the first time To solve this problem and make the function work. it reports that it has been running for the first time. Each time you call it. the function does not. Moreover. When you call it a second time. all variables defined within only exist while the function executes. Since at that point there is no $hasrun variable in this scope. Since each function creates its own scope. a new $hasrun variable is created. but when you define your function in a script and then run the script. they both represent the same scope. the variable content will also be turned into a boolean value. By using your variable first and comparing it to $true. As you have seen. reverse the order of the comparison. Keeping Information Private Often. That's another scope that may be useful. PowerShell always looks at the type to the left. Note that in place of global:. the variable type will not be changed. In a script. Here is a simple example. PowerShell by default does that for you. The same is true for most PowerShell settings because they too are defined by variables. and then run the script: $ErrorActionPreference = 'Stop' trap { "Something bad occured: $_" continue } "Starting" dir nonexisting: Get-Process willsmith "Done" . You do that by adding "global:" to your variable name. because variables and functions can only be seen by child scopes. If you run the example in the console. script: refers to the script scope. so it creates "shared variables" that are accessible from anywhere inside the script. when you set $ErrorActionPreference to 'Stop'. You will see an example of this shortly. To avoid implicit type casting. you want to make sure variables or functions do not spill over and pollute the global environment. this may result in unexpected cross-effects.} } Now the function works as expected: PS> test This function runs for the first time PS> test This function was called before There are two changes in the code that made this happen: Since all variables defined inside a function have a limited life span and are discarded once the function ends. By default. Type in the following code and save it as a script. They do not change the parent scopes. store information that continues to be present after that needs in the global scope. it is set to 'Continue'. so if that is a boolean value. Let's take a look at the ErrorActionPreference setting. you can also use script:. you can trap errors and handle them yourself. It determines what a cmdlet should do when it encounters a problem. so you want to make sure they are kept private. so PowerShell displays an error message but continues to run. MyCommand.it may become a problem in complex script solutions.ps1. and your initial script is the parent scope. That's the default behavior. prepend it with "&". By default. both errors are caught. As you can see.ps1 is executed. Since your initial script has changed $ErrorActionPreference. and error handling changes there as well. That's good because it prevents unwanted side-effects and left-overs from previously running scripts.ps1.When you run this script.and in most cases is what you need . You can see both the start message and the end message: .ps1" 'Done' Now create a second script and call it script2. Now.Definition 'Starting Script' dir nonexisting: 'Starting Subscript' & "$folder\script2. To follow the example. Once the script is done. the change to $ErrorActionPreference is automatically propagated to all child scopes. you need to call your script the default way: in the PowerShell console. Here is a little test scenario. We'll discuss this shortly. you get two error messages from PowerShell. the entire script2. this change is propagated to the second script. the change made to $ErrorActionPreference was limited to your script and did not change the setting in the parent scope. check the content of $ErrorActionPreference: PS> $ErrorActionPreference continue It is still set to 'Continue'. the second script becomes a child scope. Using Private Scopes In the previous script. Save it in the same folder: "script2 starting" dir nonexisting: Get-Process noprocess "script2 ending" When you run script2. Type in and save this code as script1.ps1: $ErrorActionPreference = 'Stop' trap { "Something bad occured: $_" continue } $folder = Split-Path $MyInvocation. enter the complete path to your script file. Note: If the script did change the global setting. If you have to place the path in quotes because of spaces. and your script controls the error messages itself. Just assume your script calls another script. While this does not seem to be a bad thing . you may have called your script "dot-sourced". Any error in script2. At C:\scripts\script2.ps1:2 char:4 + dir <<<< nonexisting: + CategoryInfo : ObjectNotFound: (nonexisting:String) [GetChildItem]. Starting Subscript script2 starting Something bad occured: Cannot find drive. That explains why the first error in script2.ps1 internally. A drive with the name 'nonexisting' does not exist. Now call script1. Here are two rules that can correct the issues: .ps1 is a child scope. so PowerShell outputs error messages and continues with the next statement. so the child script now also uses the setting "Continue".ps1 uses the statement "Continue".ps1 has propagated the ErrorActionPreference setting to the child script. The output suddenly is completely different: PS> & 'C:\scripts\script1.ps1 was output by the error handler in script1.PowerShell.Microsoft.PowerShell. script2.GetChildItemCommand Get-Process : Cannot find a process with the name "noprocess".ps1. It just does not continue in script2. It did not continue to run. DriveNotFoundException + FullyQualifiedErrorId : DriveNotFound.Commands. A drive with the name 'nonexisting' does not exist.ps1. ProcessCommandException + FullyQualifiedErrorId : NoProcessFoundForGivenName. When you look closely at the result. At C:\scripts\script2. By default.ps1' Starting Script Something bad occured: Cannot find drive.ps1 was aborted.ps1' script2 starting Get-ChildItem : Cannot find drive. Verify the process name and call the cmdlet again.ps1 now bubbles up to the next available error handler which happens to be the trap in script1. That's because an error handler always continues with the next statement that resides in the same scope the error handler is defined. script1.ps1:3 char:12 + Get-Process <<<< noprocess + CategoryInfo : ObjectNotFound: (noprocess:String) [Getprocess]. Done No PowerShell error messages anymore.ps1 which basically calls script2. the ErrorActionPreference is set to "Continue". though. you will notice though that script2. all remaining calls where skipped. A drive with the name 'nonexisting' does not exist.Microsoft. Instead.GetProcessCommand script2 ending That is expected behavior.ps1. the error handler continues. when the first error occurred.PS> & 'C:\scripts\script2. so after an error was reported. That again is default behavior: the error handler in script1.Commands. ps1 that uses private: $private:ErrorActionPreference = 'Stop' trap { "Something bad occured: $_" continue } $folder = Split-Path $MyInvocation. If you want to call child scripts without propagating information or settings. A drive with the name 'nonexisting' does not exist. o Library Script: your script is not actually performing a task but it is rather working like a library.MyCommand. make sure you also implement an error handler in that script or else the script will be aborted at the first error. If you do propagate $ErrorActionPreference='Stop' to child scripts. DriveNotFoundException + FullyQualifiedErrorId : DriveNotFound.Microsoft. It defines functions for later use. Verify the process name and call the cmdlet again. Note though that this will also prevent the changes from being visible in other child scopes such as functions you may have defined. Here is the revised script1. make sure you mark them as private:. At C:\scripts\script2.Commands.Definition 'Starting Script' dir nonexisting: 'Starting Subscript' & "$folder\script2.ps1' Starting Script Something bad occured: Cannot find drive. ProcessCommandException + FullyQualifiedErrorId : NoProcessFoundForGivenName.ps1" 'Done' And this is the result: PS> & 'C:\scripts\script1.ps1:2 char:4 + dir <<<< nonexisting: + CategoryInfo : ObjectNotFound: (nonexisting:String) [Get-ChildItem]. A drive with the name 'nonexisting' does not exist.ps1:3 char:12 + Get-Process <<<< noprocess + CategoryInfo : ObjectNotFound: (noprocess:String) [Getprocess]. Starting Subscript script2 starting Get-ChildItem : Cannot find drive.Microsoft.GetProcessComm and script2 ending . o Debugging: you want to explore variable content after a script has run.PowerShell. At C:\scripts\script2.PowerShell.GetChildItemCommand Get-Process : Cannot find a process with the name "noprocess".Commands. global scope and script scope become the same.1 you see that by default. trap { "Something bad occured: $_" continue } "script2 starting" dir nonexisting: Get-Process noprocess "script2 ending" Make sure you change script1. errors in script1.ps1. the script scope is omitted.Done Now. The user also has control over how scoping works. A drive with the name 'nonexisting' does not exist. And this is the revised script2. If the caller calls the script "dot-sourced".or put differently. all code in script2. In Figure 12. . Calling Scripts "Dot-Sourced" In the previous chapter you learned that a PowerShell developer can select the scope PowerShell should use to access a variable or function. and what would have been the script scope now is the global scope . Starting Subscript script2 starting Something bad occured: Cannot find drive. Something bad occured: Cannot find a process with the name "noprocess".ps1' Starting Script Something bad occured: Cannot find drive. and errors in script2. A drive with the name 'nonexisting' does not exist. script2 ending Done This time.ps1 back to the original version by removing "private:" again before you run it: PS> & 'C:\scripts\script1.ps1 that uses its own error handler. though.ps1 was executed and each error was handled by the new error handler in script2. the global scope (representing the PowerShell console or development environment) and the script scope (representing a script you called from global scope) are two different scopes.ps1 are handled by the built-in error handler.ps1 are handled by PowerShell. Verify the process name and call the cmdlet again. This guarantees that a script cannot change the caller‘s scope (unless the script developer used the 'global:' prefix as described earlier). Check the spelling of the name. At line:1 char:14 + test-function <<<< + CategoryInfo : ObjectNotFound: (test-function:String) []. but the global context (the console) remains unaffected and unpolluted. or if a path was included. Managing Command Types . Type in the code and save it as script3. although you cannot see the actual dot-sourcing call. There are two primary reasons to use dot-sourcing: The profile script that PowerShell runs automatically during startup ( $profile) is an example of a script that is running dot-sourced. or operable program. that may not be such a clever idea. all functions defined in the second script are also available in the first. the function test-function runs once because it is called from within the script.ps1' I am a test function! PS> test-function I am a test function! Since now the script scope and the global scope are identical. CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException Now. Dot-sourcing is more flexible because it creates the function in the caller's context. Here is a sample. PS> & 'C:\script\script3. You can no longer call test-function. function. The prefix "global:" always creates the function in the global context. Note: To make sure functions defined in a script remain accessible.ps1: function test-function { 'I am a test function!' } test-function When you run this script the default way. run the script dot-sourced! You do that by replacing the call operator "&" by a dot: PS> . 'C:\script\script3. a developer could also prepend the function name with "global:". the script did define the function test-function in the global scope. the function is gone. However. So if a script runs another script dot-sourced.ps1' I am a test function! PS> test-function The term 'test-function' is not recognized as the name of a cmdlet. verify that the path is correct and try again. script file. Once the script is done.This is how you can make sure functions and variables defined in a script remain accessible even after the script is done. That's why the function is still there once the script ended. Received = 1. Average = 2ms # Create a function having the same name: function Ping { "Ping is not allowed.10. This default behavior is completely transparent if there is no ambiguity.10.10. there is another type of scope. The scope functions live in has just a higher priority than the scope applications live in.10." } # Function has priority over external program and turns off command: ping -n 1 10. Lost = 0 (0% Loss). Each command type lives in its own scope. Ca.10.PowerShell supports a wide range of command types. Maximum = 2ms. it did not overwrite anything.10. time in millisec: Minimum = 2ms. which is an alias for Write-Output and simply outputs the parameters that you may have specified after Ping in the console.10.exe. and when you ask PowerShell to execute a command. If however you have different command types with the same name.10.10.10.10 with 32 bytes of data: Reply from 10.10: Bytes=32 Time<1ms TTL=128 Ping statistics for 10.10.10 Ping is not allowed. and when you call a command.1: Various PowerShell command types Get-Command can tell you whether there are ambiguities: Get-Command Ping .10. Actually.10 Pinging 10. CommandType Alias Function Filter Cmdlet Application ExternalScript Script Description An alias for another command added by using Set-Alias A PowerShell function defined by using function A PowerShell filter defined by using filter (a function with a process block) A PowerShell cmdlet from a registered snap-in An external Win32 application An external script file with the file extension ".ps1" A scriptblock Priority 1 2 2 3 4 5 - Table 12. Ping calls the Echo command.10. your function was able to "overwrite" ping. As you can see. this may lead to surprising results: # Run an external command: ping -n 1 10.10.10 Now. Aliases live in yet another scope which has the highest priority of them all: Set-Alias ping echo ping -n 1 10. it searches the command type scopes in a specific order.10 -n 1 10.10: Packets: Sent = 1. PowerShell searches for commands in a specific order. the content of scopes is visible to all child scopes and does not change any parent scope. you can use Get-Command to retrieve the command type.10. private: and local:.CommandType Name -------------function Ping allowed.exe. at the end of a day most useful information breaks down to plain text. Ca. So in the example above. To find the command. If the command name is ambiguous. Often. So while the objectoriented approach of PowerShell is a great thing. and script. By default. In this chapter. Instead. cmdlet.10.10 with 32 bytes of data: Reply from 10. Average = 2ms Summary PowerShell uses scopes to manage the life span and visibility of variables and functions. you'll learn how to control text information in pretty much any way you want.10. A different flavor of scope is used to manage the five different command types PowerShell supports. for the element you are calling. .10 Pinging 10. Maximum = 2ms.EXE C:\Windows\system32\PING. it searches the command type scopes in this order: alias.10. function. or you would like to isolate the file name from a file path." Alias ping Application PING. With dot sourcing. The developer can control the scope to use by prepending variable and function names with one of these keywords: global:. The user can control scope by optionally dot-sourcing scripts. functions or script blocks. application. There is always at least one scope which is called "global scope". Here. Received = 1. external script.10: Packets: Sent = 1. use this: # Get command named "Ping" with commandtype "Application": $command = Get-Command Ping -CommandType Application # Call the command & $command -n 1 10. The prefix local: is the default and can be omitted. Use Get-Command to locate a command yourself based on name and command type if you need more control.10.10.10. Lost = 0 (0% Loss).EXE Definition ---------"Ping is not echo Invoking a Specific Command Type To make sure you invoke the command type you are after. PowerShell uses the first command it finds. You may want to read the content from some text file and extract lines that contain a keyword. you need to deal with plain text information. no new scope is created.10. and then execute it with the call operator "&". New scopes are created when you define scripts or functions. to explicitly call ping. script:. time in millisec: Minimum = 2ms. the caller's context is used.10: Bytes=32 Time<1ms TTL=128 Ping statistics for 10. Have a look: $text = 'This text may also contain $env:windir `: $(2+2)' This text may also contain $env:windir `: $(2+2) Placed in single quotes. PowerShell returns the text exactly like you entered it. If you want PowerShell to treat the text exactly the way you type it. and PowerShell replaces the variable with its context. use single quotes. With double quotes. place it in quotes. Use double quotes with care because they can transform your text: any variable you place in your text will get resolved.Topics Covered: Defining Text o Special Characters in Text o Resolving Variables o "Here-Strings": Multi-Line Text o Communicating with the User Composing Text with "-f" o Setting Numeric Formats o Outputting Values in Tabular Form: Fixed Width o String Operators o String Object Methods o Analyzing Methods: Split() as Example Simple Pattern Recognition Regular Expressions o Describing Patterns o Quantifiers o Anchors o Recognizing Addresses o Validating E-Mail Adddresses o Simultaneous Searches for Different Terms o Case Sensitivity o Finding Information in Text o Searching for Several Keywords o Forming Groups o Greedy or Lazy? Shortest or Longest Possible Result o Finding Segments o Replacing a String o Using Back References o Putting Characters First at Line Beginnings o Removing White Space o Finding and Removing Doubled Words Summary Defining Text To define text. the result is completely different: $text = "This text may also contain $env:windir `: $(2+2)" This text may also contain C:\Windows: 4 . you can insert special control characters like tabs or line breaks by adding a backtick and then a special character where "t" stands for a tab and "n" represents a line break.Special Characters in Text The most common "special" character you may want to put in text are quotes. When you use double quotes to delimit text. Quotes are tricky because you need to make sure that PowerShell does not confuse the quotes inside your text with the quotes that actually surround and define the text. Again. This technique does require that the text is defined by double quotes: PS> "One line`nAnother line" One line Another line PS> 'One line`nAnother line' One line`nAnother line Escape Sequence `n `r `t `a `b `' `" `0 `` Special Characters New line Carriage return Tabulator Alarm Backspace Single quotation mark Double quotation mark Null Backtick character Table 13. or by placing a "backtick" character in front of the quote: 'The "The 'The "The ''situation'' ""situation"" `'situation`' `"situation`" was was was was really really really really not not not not that that that that bad' bad" bad' bad" The second most wanted special character you may want to include in text is a new line so you can extend text to more than one line. you can "escape" quotes (remove their special meaning) by either using two consecutive quotes. you have a couple of choices. You do have a couple of choices.1: Special characters and "escape" sequences for text Resolving Variables . you can freely use double quotes inside the text. and vice versa: 'The "situation" was really not that bad' "The 'situation' was really not that bad" If you must use the same type of quote both as delimiter and inside the text. If you used single quotes to delimit the text. To make PowerShell treat these brackets as it would outside of text. "Here-Strings": Multi-Line Text As you have seen.44MB) diskettes. $$ is again a variable (it is an internal "automatic" variable maintained by PowerShell which happens to contain the last command token PowerShell processed which is why the result of the previous code line can vary and depends on what you executed right before). They work like quotes except they use a "@" before and after the quote to indicate that the text extends over multiple lines. While that may work for one or two lines of text.A rather unusual special character is "$".$extension" report. it quickly becomes confusing for the reader and tiresome for the script author to construct strings like that. Now. PowerShell ignores variables and treats "$" as a normal character: 'Hello Mr $name' At the same time. double quotes protect you from unwanted variable resolving. Take a look at this example: "My wallet is low on $$$$" As turns out. you should start using single quotes by default unless you really want to resolve variables in your text." $result One CD has the capacity of 500 diskettes. If you use single quotes. Resolving text can be enormously handy: PS> $name = "report" PS> $extension = "txt" PS> "$name. what would you do if you needed to use "$" both to resolve variables and to display literally in the same text? Again.txt Just make sure you use it with care. you can use the backtick to escape the "$" and remove its special resolving capability: "The variable `$env:windir contains ""$env:windir""" Tip: You can use the "$" resolving capabilities to insert live code results into text. Just place the code you want to evaluate in brackets. Text in double quotes also honors this special character and recognizes variables by resolving them: PowerShell automatically places the variable content into the text: $name = 'Weltner' "Hello Mr $name" This only works for text enclosed in double quotes. . place a "$" before: $result = "One CD has the capacity of $(720MB / 1. so as a rule of thumb. A much more readable way is using here-strings. PowerShell uses it to define variables that can hold information. you can insert special backtick-key-combinations to insert line breaks and produce multi-line text. Marshal]::PtrToStringAuto([Runtime. PowerShell uses this method internally when you define text in double quotes: # Query and output text entry by user: $text = Read-Host "Your entry" Your entry: $env:windir $text $env:windir # Treat entered text as if it were in double quotation marks: $ExecutionContext. To accept plain text input use Read-Host: $text = Read-Host "Enter some text" Enter some text: Hello world! $text Hello world! Text accepted by Read-Host is treated literally. To convert an encrypted SecureString into plain text. Communicating with the User Maybe you don't want to hard-code text information in your script at all but instead provide a way for the user to enter information.Security. so it behaves like text enclosed in single quotes. Nevertheless. you can however send it to the internal ExpandString() method for post-processing. here.M arshal]::SecureStringToBSTR($pwd)) . Read-Host won't return plain text anymore but instead an encrypted SecureString. So.InteropServices. >> "@ >> $text Here-Strings can easily stretch over several lines and may also include "quotation marks".$text = @" >> Here-Strings can easily stretch over several lines and may also include >>"quotation marks". Special characters and variables are not resolved. If you want to resolve the text a user entered.InvokeCommand. however. The text will be concluded only if you terminate the >> here-string with the termination symbol "@. not only the input was masked with asterisks. you can use some internal .ExpandString($text) C:\Windows You can also request secret information from a user.NET methods: $pwd = Read-Host -asSecureString "Password" Password: ************* $pwd System. To mask input. and subexpressions like 4 are likewise replaced >> with their result. too. the result is just as unreadable. too. use the switch parameter -asSecureString. Nevertheless. The text will be concluded only if you terminate the here-string with the termination symbol "@. variables are replaced with their values: C:\Windows.SecureString [Runtime. This time. and subexpressions like 4 are likewise replaced with their result. variables are replaced with >> their values: C:\Windows. here.InteropServices. then the calculation should be in parentheses.44mb) 500 diskettes per CD The -f format operator formats a string and requires a string. the specified width will be ignored. For example. However. the result is processed instead of the parentheses. Every wildcard used has the following formal structure: {index[. The index number is the only obligatory specification. if the value is narrower than the specified width. "diskettes" 500 diskettes at 720MB fit into one CD at 1. The number in the braces states which value will appear later in the wildcard and in which order: "{0} {3} at {2}MB fit into one CD at {1}MB" -f (720mb/1. 1.44mb). If the value is wider than the specified width. As is generally true in PowerShell.44mb You may use as many wildcard characters as you wish. You'll soon be using it to format numeric values for easier reading: "{0:0} diskettes per CD" -f (720mb/1. This allows columns to be set flush. along with wildcards on its left side and on its right side. . the width will be filled with blank characters.44mb Bad numeric constant: 754974720 diskettes per CD. The other two specifications are voluntary. Alignment: Positive or negative numbers can be specified that determine whether the value is right justified (positive number) or left justified (negative number).alignment][:format]}: Index: This number indicates which value is to be used for this wildcard. that the results are to be inserted into the string instead of the wildcards: "{0} diskettes per CD" -f (720mb/1.44MB Setting Numeric Formats The –f format operator can insert values into text as well as format the values. the parentheses ensure that the enclosed statement is evaluated first and separately and that subsequently. The number states the desired width.44mb) 500 diskettes per CD It is absolutely necessary that exactly the same results are on the right side that are to be used in the string are also on the left side. or in various display formats. At line:1 char:33 + "{0} diskettes per CD" -f 720mb/1 <<<< .strictly confidential Composing Text with "-f" The –f format operator is the most important PowerShell string operator. -f would report an error: "{0} diskettes per CD" -f 720mb/1. you could use several wildcards with the same index if you want to output one and the same value several times. If you want to just calculate a result.44. Without parentheses. 720. "t".0000}" -f $value "{0:c}" -f $value "{0:d}" -f $value "{0:e}" -f $value "{0:00e+0}" -f $value "{0:f}" -f $value "{0:g}" -f $value "{0:n}" -f $value "0x{0:x4}" -f $value Result (1000000) 100000000% 1.ToString($format) } DATE with d : 10/15/2007 DATE with D : Monday.000 1000 1000000."U"."s". Format: The value can be formatted in very different ways.000 There's also a very wide range of time and date formats. You'll find an overview of available formats below. Formatting statements are case sensitive in different ways than what is usual in PowerShell."m"."F". You can see how large the differences can be when you format dates: # Formatting with a small letter d: "Date: {0:d}" -f (Get-Date) Date: 08/28/2007 # Formatting with a large letter D: "Date: {0:D}" -f (Get-Date) Date: Tuesday."u"."T"..4 and their operation is shown in the following lines: $date= Get-Date foreach ($format in "d". 15 October.000000e+006 10e+5 1000000.00 € 1000000 1.000.} " -f $value "{0:0."r".000."M/yy".000.000.000."dd-MM-yy") { "DATE with $format : {0}" -f $date."D". .0000 1."y".000 Decimal point 0 placeholder Currency Decimal Scientific notation Exponent wildcard Fixed point General Thousands separator Hexadecimal Call "{0:(#).0}" -f $value "{0:00."f".0 1000000.0}" -f $value "{0:0. 2007 DATE with f : Monday.."G". .00 1000000 1. you can format numbers quickly and comfortably. 0 c d e e f g n x Type Digit placeholder Percentage Thousands separator Integral multiple of 1.000. The relevant formats are listed in Table 13. August 28.000. No need for you to squint your eyes any longer trying to decipher whether a number is a million or 10 million: 10000000000 "{0:N0}" -f 10000000000 10.` "dddd. 2007 Symbol # % .00 0x4240 Table 13. MMMM dd yyyy". 15 October. 2007 02:17 PM .##}" -f $value "{0:0%}" -f $value "{0:0."g".3.3: Formatting numbers Using the formats in Table 13. Here you can use the relevant format name to specify the format you wish. 15 Oct 2007 02:17:02 GMT s : 2007-10-15T02:17:02 t : 02:17 PM T : 02:17:02 PM u : 2007-10-15 02:17:02Z U : Monday.Byte System. 2007 02:17:02 PM g : 10/15/2007 02:17 G : 10/15/2007 02:17:02 m : October 15 r : Mon.Guid . 15 October. 2007 dddd. 15 October.Name -eq "tostring"} |%{"$_"}.Double System.GetMethods() | Where-Object {$_. if ($methods -eq "System.String ToString(System.NET types that support the toString() method: [AppDomain]::CurrentDomain.DATE DATE DATE DATE DATE DATE DATE DATE DATE DATE DATE DATE DATE DATE with with with with with with with with with with with with with with F : Monday.Enum])} } | ForEach-Object { $Methods = $_.Decimal System. October 15 2007 M/yy : 10/07 dd-MM-yy : 15-10-07 Call "{0:d}" -f $value "{0:D}" -f $value "{0:t}" -f $value "{0:T}" -f $value "{0:f}" -f $value "{0:F}" -f $value "{0:g}" -f $value "{0:G}" -f $value "{0:M}" -f $value "{0:r}" -f $value "{0:s}" -f $value "{0:u}" -f $value "{0:U}" -f $value "{0:Y}" -f $value Result 09/07/2007 Friday.GetAssemblies() | ForEach-Object { $_. you need only look for . September 7. September 7. 07 Sep 2007 10:53:56 GMT 2007-09-07T10:53:56 2007-09-07 10:53:56Z Friday. 2007 10:53 AM 10:53:56 AM Friday. 2007 10:53:56 AM 09/07/2007 10:53 AM 09/07/2007 10:53:56 AM September 07 Fri.DateTime System.GetExportedTypes() | Where-Object {! $_. 2007 00:17:02 y : October. 2007 08:53:56 September 2007 Symbol d D t T f F g G M r s u U Y Type Short date format Long date format Short time format Long time format Full date and time (short) Full date and time (long) Standard date (short) Standard date (long) Day of month RFC1123 date format Sortable date format Universally sortable date format Universally sortable GMT date format Year/month format pattern Table 13. 2007 10:53 AM Friday. September 7.FullName } } System.String)") { $_. September 7.Enum System. MMMM dd yyyy : Monday.4: Formatting date values If you want to find out which type of formatting options are supported.IsSubclassOf([System.Convert System. here's a brief example showing how to create and format a GUID: $guid = [GUID]::NewGUID() foreach ($format in "N".Int32 System.Guid.Int16 System.PowerShell."P") {"GUID with $format : {0}" -f $GUID. . D.SByte System.ToString($format)} GUID with N : 0c4d2c4c8af84d198b698e57c1aee780 GUID with D : 0c4d2c4c-8af8-4d19-8b69-8e57c1aee780 GUID with B : {0c4d2c4c-8af8-4d19-8b69-8e57c1aee780} GUID with P : (0c4d2c4c-8af8-4d19-8b69-8e57c1aee780) Symbol dd ddd dddd gg hh HH mm MM MMM MMMM ss tt yy yyyy zz zzz Type Day of month Abbreviated name of day Full name of day Era Hours from 01 to 12 Hours from 0 to 23 Minute Month Abbreviated month name Full month name Second AM or PM Year in two digits Year in four digits Time zone including leading zero Time zone in hours and minutes Call "{0:dd}" -f $value "{0:ddd}" -f $value "{0:dddd}" -f $value "{0:gg}" -f $value "{0:hh}" -f $value "{0:HH}" -f $value "{0:mm}" -f $value "{0:MM}" -f $value "{0:MMM}" -f $value "{0:MMMM}" -f $value "{0:ss}" -f $value "{0:tt}" -f $value "{0:yy}" -f $value "{0:YY}" -f $value "{0:zz}" -f $value "{0:zzz}" -f $value Result 07 Fri Friday A.UInt64 Microsoft.System. A format operator can set outputs to a fixed width.Single System.MatchInfo For example."D".Commands.IntPtr System. Because you'll frequently require GUID. among the supported data types is the "globally unique identifier" System.UInt16 System. which is clearly understood worldwide."B". 10 10 53 09 Sep September 56 07 2007 +02 +02:00 Table 13.Int64 System.5: Customized date value formats Outputting Values in Tabular Form: Fixed Width To display the output of several lines in a fixed-width font and align them one below the other. each column of the output must have a fixed width.UInt32 System. csv = 307 Bytes info.txt = 8562 Bytes layout. It takes a static string template with placeholders and an array with values.txt = 164186 Bytes p1. reassemble. Dynamic methods: the String data type.name.nrproj = 5808 Bytes ping. Dir returns a directory listing. the String .2). You heard about this operator at the beginning of this chapter. Positive numbers will set values to right alignment. They can be used to automatically join together an array or to split a text into an array of substrings.10} Bytes" -f $_.bat SilentlyContinue { "{0.bat = 116 Bytes SilentlyContinue = 0 Bytes The following result with fixed column widths is far more legible. Two additional important string operators are -join and -split.Length } Bytes Bytes Bytes Bytes Bytes Bytes Bytes More options are offered by special text commands that PowerShell furnishes from three different areas: String operators: PowerShell includes a number of string operators for general text tasks which you can use to replace text and to compare text (Table 13. $_.6).name) = $($_. String Operators All string operators work in basically the same way: they take data from the left and the right and then do something with them.NET class includes static methods bound to no particular text. and then fills the values into the placeholders.nrproj ping.-20} = = 307 = 8562 = 1280 = 164186 = 5808 = 116 = 0 {1.txt layout. which saves text.txt p1. the result is ragged right and hard to read: dir | ForEach-Object { "$($_. Static methods: finally. "Eddie" Hello Eddie The format operator -f works in exactly the same way. add a comma to the sequential number of the wildcard and after it specify the number of characters available to the wildcard.csv info. from which a subsequent loop outputs file names and file sizes.lxy list. Because file names and sizes vary. negative numbers to left alignment: dir | ForEach-Object history. To set widths. The –replace operator for example takes a text and some replacement text and then replaces the replacement text in the original text: "Hello Carl" -replace "Carl". dismantle. includes its own set of text statements that you can use to search through. and modify text in diverse ways (Table 13.Length) Bytes" } history. .lxy = 1280 Bytes list.In the following example. CompareTo("Hello") ("Hello"). ') Installed MUI-Languages: de-DE.")+1 ) bat Another approach uses the dot as separator and Split() to split up the path into an array. Text is stored in a String object.Let's say you want to output information that really is an array of information. and a string object has built-in methods for manipulating the text information. and each time it discovers the split pattern.LastIndexOf(". Here is how: PS> $mui = Get-WmiObject Win32_OperatingSystem | Select-Object ExpandProperty MuiLanguages PS> 'Installed MUI-Languages: {0}' -f ($mui -join '. To auto-escape a simple text pattern.Substring( $path. The Escape() method takes a simple text pattern and returns the escaped version that you can use wherever a regular expression is needed: PS> [RegEx]::Escape('some. So. the result can be an array (when more than one language is installed). It takes a text and a split pattern.bat" $path. Simply add a ". The result is that the last element of the array (-1 index number) will include the file extension: $path.NET methods." and then the method you need: $path = "c:\test\Example. This example illustrates how you can use -split to parse a path: PS> ('c:\test\folder\file. en-US The -split operator does the exact opposite.Split(".\pattern') some\.Contains("ll") .txt Note that -replace expects the pattern to be a regular expression. Note also that the Split-Path cmdlet can split paths more easily. so if your pattern is composed of reserved characters (like the backslash).")[-1] bat Function CompareTo() Contains() Description Compares one string to another Returns "True" if a specified comparison string is in a string or if the comparison string is empty Example ("Hello"). you have to escape it.\\pattern String Object Methods You know from Chapter 6 that PowerShell represents everything as objects and that every object contains a set of instructions known as methods. this line produces an incomplete output: You would have to join the array to one string first using -join. use . When you query WMI for your operating system to identify the installed MUI languages.txt' -split '\\')[-1] file. it splits the original text in chunks and returns an array. LastIndexOfAny("loe") ("Hello").StartsWith("He") ("Hello World").IndexOf("l") ("Hello"). $a.Insert(6.TrimEnd() + "World" (" Hello ").toUpper() ("Hello World").Replace("l".toLowerInvariant() ("Hello World"). 5) $a ("Hello").toLower() ("Hello World"). "x") ("Hello World").CopyTo() Copies part of a string to another string $a = ("Hello World"). Just as a quick refresher.GetEnumerator() ("Hello"). .Trim() + "World" (" Hello ").TrimStart() + "World" ("Hello").PadRight(10) + "World!" ("Hello World").PadLeft(10) ("Hello").Substring(4.ToUpperInvariant() (" Hello ").Equals($a) ("Hello").Chars(0) EndsWith() Equals() Tests whether the string ends with a specified string Tests whether one string is identical to another string Returns the index of the first occurrence of a comparison IndexOf() string Returns the index of the first occurrence of any character in IndexOfAny() a comparison string Insert() Inserts new string at a specified index in an existing string Retrieves a new object that can enumerate all characters of GetEnumerator() a string Finds the index of the last occurrence of a specified LastIndexOf() character Finds the index of the last occurrence of any character of a LastIndexOfAny() specified string Pads a string to a specified length and adds blank characters PadLeft() to the left (right-aligned string) Pads string to a specified length and adds blank characters PadRight() to the right (left-aligned string) Removes any requested number of characters starting from Remove() a specified position Replace() Replaces a character with another character Converts a string with specified splitting points into an Split() array StartsWith() Tests whether a string begins with a specified character Substring() Extracts characters from a string ToCharArray() Converts a string into a character array ToLower() Converts a string to lowercase Converts a string to lowercase using casing rules of the ToLowerInvariant() invariant language ToUpper() Converts a string to uppercase Converts a string to uppercase using casing rules of the ToUpperInvariant() invariant language Trim() Removes blank characters to the right and left TrimEnd() Removes blank characters to the right TrimStart() Removes blank characters to the left Chars() Provides a character at the specified position Table 13.EndsWith("lo") ("Hello").Remove(5.toCharArray() ("Hello World"). 3) ("Hello World").CopyTo(0.Split("l") ("Hello World").6: The methods of a string object Analyzing Methods: Split() as Example You already know in detail from Chapter 6 how to use Get-Member to find out which methods an object contains and how to invoke them. let's look again at an example of the Split() method to see how it works. 6.6) ("Hello World"). "brave ") ("Hello").IndexOfAny("loe") ("Hello World").toCharArray() ("User!").LastIndexOf("l") ("Hello"). Int32 count) System.String[] Split(Char[] separator.definition System. That makes the result much more understandable: ("something" | Get-Member Split).String[] Split(Char[] separator.String[] Split(Char[] separator.String[] Split(String[] separator. StringSplitOptions options) Definition gets output. StringSplitOptions options) System.String[] Split(String[] separator. System. Int32 count.String[] Split(Params Char[] separator) System. System. you will expect a character array and will use every single character as a possible splitting separator.String[] Split(String[] separator.String[] Split(String[] separator.String[] Split(Char[] separator. to insert a line break where appropriate.e.Split(". StringSplitOptions options) System. System.String[] Split(Char[] separator. System.") a b c d e f If the splitting separator itself consists of several characters. StringSplitOptions options). Int32 count).Definition.6. Int32 count. StringSplitOptions options).c. If you want to use the first signature. the first argument must be of the String[] type and the second argument of the StringSplitOptions type. The simplest way for you to meet this requirement is by assigning arguments first to a strongly typed variable. System. StringSplitOptions options) System. That's important because it means that you may use several separators at once: "a. ". StringSplitOptions options) You must make sure that you pass data types to the signature that is exactly right for it to be able to use a particular signature. you can use methods from Table 13. StringSplitOptions options). Split().b. Because Definition is also a string object.d.("something" | Get-Member Split).String[] Split(String[] separator. In simple cases. including Replace().. Create the variable of exactly the same type that the signature requires: # Create a variable of the [StringSplitOptions] type: [StringSplitOptions]$option = "None" . StringSplitOptions options) System. ")`n") System. StringSplitOptions options) There are six different ways to invoke Split(). then it has got to be a string and not a single Char character. Int32 count.Replace("). you might use Split() with only one argument. Int32 count.String[] Split(Params Char[] separator).f". There are only two signatures that meet this condition: System. but it isn't very easy to read.String[] Split(String[] separator. Int32 count.String[] Split(Char[] separator. In fact. For example. There does remain the question of how do you know it is necessary to assign the value "None" to the StringSplitOptions data type.b.d.b. You could use it to remove redundant blank characters from a text: [StringSplitOptions]$option = "RemoveEmptyEntries" "This text has too much whitespace".f g Split() in fact now uses a separator consisting of several characters.c d. what was RemoveEmptyEntries() able to accomplish? If Split() runs into several separators following each other.Split($separator.StringSplitOptions" due to invalid enumeration values. it is not in a String object but in the String class. It splits the string only at the points where it finds precisely the characters that were specified.# Create a variable of the String[] type: [string[]]$separator = ".c. At line:1 char:28 + [StringSplitOptions]$option <<<< = "werner wallbach" By now it should be clear to you what the purpose is of the given valid values and their names.. The simple answer is: you don‘t know and it isn‘t necessary to know.e. If you assign a value to an unknown data type that can't handle the value." # Invoke Split with the wished signature and use a two-character long separator: ("a. the data type will automatically notify you of all valid values: [StringSplitOptions]$option = "werner wallbach" Cannot convert value "werner wallbach" to type "System. $option) This text has too much whitespace Now all you need is just a method that can convert the elements of an array back into text. A simple form of wildcards was invented for the file system many years ago and it still works today. such as to determine whether a user has given a valid network ID or valid e-mail address. RemoveEmptyEntries". The method is called Join().g").. $option) a. Simple Pattern Recognition Recognizing patterns is a frequent task that is necessary for verifying user entries. Specify one of the following enumeration values and try again..e.Split(" ".f. The possible enumeration values are "None. you've probably used it before in one form or another: # List all files in the current directory that have the txt file extension: . RemoveEmptyEntries() deletes such empty entries. empty array elements will be the consequence. t?? # List all files that end in one of the letters from "e" to "z" dir *[e-z]." if ($ip -like "*.6666.*.*") { "valid" } else { "invalid" } If you want to verify whether a valid e-mail address was entered.*.*" True Regular Expressions Use regular expressions for more accurate pattern recognition.7 work in the file system.* # List all files whose file extensions begin with "t" and which are exactly 3 characters long: Dir *.*. regular expressions are also much more complicated.@. if you want to verify whether a user has given a valid IP address.*" These simple patterns are not very exact. that's why they can describe patterns in much greater detail.txt # List all files in the Windows directory that begin with "n" or "w": dir $env:windir\[nw]*.7: Using simple placeholders The placeholders in Table 13. .* Table 13." $email -like "*.*.* Wildcard * ? [xyz] [x-z] Description Any number of any character (including no characters at all) Exactly one of any characters One of specified characters One of the characters in the specified area Example Dir *.werner. however. you could check the pattern like this: $email = "tobias. For example. For the very same reason. Regular expressions offer highly specific wildcard characters.Dir *.de" $email -like "*.* Dir *[p-z].txt Dir *. you could do so in the following way: $ip = Read-Host "IP address" if ($ip -like "*.*@*. but also with string comparisons like -like and -notlike.*") { "valid" } else { "invalid" } valid # The following invalid e-mail address was not identified as false: $email = ". though: # Wildcards are appropriate only for very simple pattern recognition and leave room for erroneous entries: $ip = "300.weltner@powershell.*@*.??t Dir [abc]*. within which the characters are specified that the wildcard represents. For example. The pattern represented by a regular expression may consist of four different character types: Literal characters like "abc" that exactly matches the "abc" string. define a 3-digit number or a 6-character-word. tab. If you want to use any character except for the specified characters. These elements are grouped into three categories: Placeholder: The placeholder represents a specific type of data. equivalent to ASCII 0 to ASCII 26 A number (equivalent to [0-9]) Any character except for numbers Escape (ASCII 9) Form feed (ASCII 15) New line Carriage return Any whitespace character like a blank character. for example.Describing Patterns Using the regular expression elements listed in Table 13. for example a character or a digit. [^abc] [^a-z] [abc] [a-z] \a \c \cA-\cZ \d \D \e \f \n \r \s \S \t \uFFFF \v \w \W \xnn . use "^" as the first character in the square brackets. and "h". the Euro symbol has the code 20AC Vertical tab (ASCII 11) Letter. you can describe patterns with much greater precision. or line break Tab character Unicode character with the hexadecimal code FFFF. You could. Anchor: Allows you to determine whether a pattern is bound to a specific boundary. Description Exactly one character of any kind except for a line break (equivalent to [^\n]) All characters except for those specified in brackets All characters except for those in the range specified in the brackets One of the characters specified in brackets Any character in the range indicated in brackets Bell alarm (ASCII 7) Any character allowed in an XML name Control+A to Control+Z.11. Quantifier: Allows you to determine how often a placeholder occurs in a pattern. Masked or "escaped" characters with special meanings in regular expressions. ^ $ * + ? { [ ] \ | ( )". For example. For example. You could define a pattern that needs to be a separate word or that needs to begin at the beginning of the text. "g". The following characters have special meanings and for this reason must be masked if used literally: ". or underline Any character except for letters Particular character. digit. where nn specifies the hexadecimal ASCII code Element . Pre-defined wildcard characters that represent a particular character category and work like placeholders. tab. Custom wildcard characters: They consist of square brackets. when preceded by "\". the placeholder "[^f-h]" stands for all characters except for "f". "\d" represents any number from 0 to 9. or line break Any character except for a blank character. they are understood as literal characters: "\[test\]" looks for the "[test]" string. 3}\. including multi-line texts Must match at beginning of a string (\A is less ambiguous for multi-line texts) Table 13.3}\.3}" finds numbers only up to three digits if these turn up separately in a string. Elements $ \A \b \B \Z ^ Description Matches at end of a string (\Z is less ambiguous for multi-line texts) Matches at beginning of a string. Using quantifiers. Element * *? . For example. Usually. the regular expression "\b\d{1.10.\d{1.m} {n} + Description Preceding expression is not matched or matched once or several times (matches as much as possible) Preceding expression is not matched or matched once or several times (matches as little as possible) Any number of any character (including no characters at all) Preceding expression is not matched or matched once (matches as much as possible) Preceding expression is not matched or matched once (matches as little as possible) n or more matches Inclusive matches between n and m Exactly n matches Preceding expression is matched once Table 13.10: Anchor boundaries Recognizing IP Addresses Patterns such as an IP address can be very precisely described by regular expressions. The number "123" in the string "Bart123" would not qualify.* ? ?? {n. you can tell how many instances are parts of your pattern.3}" represents a number occurring one to three times for a one-to-three digit number.\d{1. For example..9: Quantifiers for patterns Anchors Anchors determine whether a pattern has to match a certain boundary.3}\b" True . including multi-line texts Matches on word boundary (first or last characters in words) Must not match on word boundary Must match at end of string.8: Placeholders for characters Quantifiers Every pattern listed in Table 13. "\d{1.10.* Any number of any character (including no characters at all) Table 13.8 represents exactly one instance of that kind.\d{1.10" $ip -match "\b\d{1. you would use a combination of characters and quantifiers to specify which characters may occur in a string and how often: $ip = "10.3}\.} {n. This dot is introduced with a "\" character because the dot actually has a different meaning in regular expressions if it isn't within square brackets.4}) again follows the square brackets.10" $ip -match "\b\d{1._%+-").3}\.4}\b" Whenever you look for an expression that occurs as a single "word" in text.) in the e-mail address follows.3}\.3}\. use the following regular expression: $email $email True $email $email False = "test@somewhere.[A-Z]{2. The regular expression subsequently specifies which characters may be included in an e-mail address.$ip = "a.3}\.\d{1.\d{1.400. However. delimit your regular expression by word boundaries (anchor: \b).\d{1.-]+\.500. After the dot is the domain identifier.\d{1. Checking is far from perfect since it is not verified whether the numbers really do lie in the permitted number range from 0 to 255. or line breaks. after it a text again having the same characters as those in front of "@".3}\b" True Validating E-Mail Addresses If you'd like to verify whether a user has given a valid e-mail address.3}\b" False The pattern is described here as four numbers (char: \d) between one and three digits (using the quantifier {1. Following this is "@" and.10. However.10.3}\.3}) and anchored on word boundaries (using the anchor \b)." -match "\b[A-Z0-9.@. "A-Z0-9") and single characters (such as ". this regular expression still has one flaw.com and reply!" . tabs.10" $ip -match "\b\d{1.-]+\.999" $ip -match "\b\d{1.\d{1. Permissible characters are in square brackets and consist of "ranges" (for example. The regular expression will then know you're interested only in those passages that are demarcated from the rest of the text by white space like blank characters. or line breaks.10.\d{1. The backslash ensures that the regular expression understands the dot behind it literally.4}\b" = ". A dot (\.\d{1. tabs.[A-Z]{2.com" -match "\b[A-Z0-9._%+-]+@[A-Z0-9.10. # There still are entries incorrectly identified as valid IP addresses: $ip = "300. While it does verify whether a valid e-mail address is in the text somewhere.3}\b" False $ip = "1000. which may consist solely of letters ( [A-Z] ).3}\. you can also stipulate as many more characters as you wish.\d{1.3}\.3}\. It specifies that the domain identifier may consist of at least two and at most four of the given characters. there could be another text before or after it: $email = "Email please to test@somewhere._%+-]+@[A-Z0-9. meaning that it is surrounded by white space like blank characters. if you like.3}\.\d{1. The "+" behind the square brackets is a quantifier and means that at least one of the given characters must be present. A quantifier ({2. be in the specified location in the pattern._%+-]+@[A-Z0-9. Other quantifiers are "*" (may also match more than one character) and "+" (must match characters at least once). as you might expect after using simple wildcards. "u?" ensures that the letter "u" may. In the example.[A-Z]{2. it only takes into account word boundaries.4}\b" True Because of "\b". which are placed in parentheses.$email -match "\b[A-Z0-9. use sub-expressions again: # finds "and Bob": "Peter and Bob" -match "and (Bob|Willy)" True # does not find "and Bob": "Bob and Peter" -match "and (Bob|Willy)" False . therefore. $email -match "^[A-Z0-9. but doesn't have to._%+-]+@[A-Z0-9.-]+\.4}$" Simultaneous Searches for Different Terms Sometimes search terms are ambiguous because there may be several ways to write them. turn up in the search term: "color" -match "colou?r" True "colour" -match "colou?r" True The "?" character here doesn't represent any character at all. use the elements for sentence beginnings (anchor: "^") and endings (anchor: "$") instead of word boundaries. In simple cases put a "?" after an optional character.[A-Z]{2. If you prefer to mark more than one character as optional. when your regular expression searches for a pattern somewhere in the text. "?" is a quantifier and always specifies how often a character or expression in front of it may occur. The following example recognizes both the month designator "Nov" and "November": "Nov" -match "\bNov(ember)?\b" True "November" -match "\bNov(ember)?\b" True If you'd rather use several alternative search terms. If you prefer to check whether the entire text corresponds to an authentic e-mail. Then the character in front of "?" may. but not necessarily. For regular expressions.-]+\. put the character in a sub-expression. use the OR character "|": "Bob and Ted" -match "Alice|Bob" True And if you want to mix alternative search terms with fixed text. You can use the "?" quantifier to mark parts of the search term as optional. Fortunately.NET framework RegEx object instead of –match. This explains why the word "test" in the below example is recognized only if its last two characters are lowercase. we'll look more closely at a complex regular expression that evidently is entirely made up of the conventional elements listed in Table 13. Use the operator -cmatch as alternative if you'd prefer case sensitivity: # -match is case insensitive: "hello" -match "heLLO" True # -cmatch is case sensitive: "hello" -cmatch "heLLO" False If you want case sensitivity in only some pattern segments. the character is not recognized as a formatting character but as a \ literal character x? Changes the x quantifier into a "lazy" quantifier (?xyz) Activates of deactivates special modes.Case Sensitivity In keeping with customary PowerShell practice. a regular expression can perform any number of detailed checks. If you prefer case insensitivity.11: Category Selection Escape Option Option Option Reference Reference . either use the above construct to specify the option (i?) in your regular expression or submit extra options to the Matches() method (which is a lot more work): [regex]::matches("test". It's enough to know which regular expression to use for a particular pattern. use –match. Conversely. such as verifying whether numbers in an IP address lie within the permissible range from 0 to 255. case sensitivity x+ Turns the x quantifier into a "greedy" quantifier ?: Does not backtrack ?<name> Specifies name for back references Table 13. while case sensitivity has no importance for the first two characters: "TEst" -match "(?i)te(?-i)st" True "TEST" -match "(?i)te(?-i)st" False If you use a . among others. anything following "(?-i)" is case sensitive.11: Regular expression elements Of course. Regular expressions for nearly all standard patterns can be downloaded from the Internet. it will work case-sensitive by default. specify in your regular expression which text segments are case sensitive and which are insensitive. Anything following the "(?i)" construct is case insensitive. "IgnoreCase") Element Description (xyz) Sub-expression | Alternation construct When followed by a character. you generally won't need to invest much time in learning complex regular expressions like the ones coming up. much like – cmatch. the -match operator is case insensitive. "TEST". Also. In the following example. The problem is that this makes regular expressions long and hard to understand. 3}\. my e-mail address is tobias@powershell. This construct is supposed to be present three times ( {3}).10" $ip -match "\b\d{1. so you should now be able to shorten the first used IP address regular expression: $ip = "10.\d{1. 2[0-4][0-9] is a number from 200 through 249. the following expression includes a definition of the number: (?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.-]+\. The result is that the sub-expression describes numbers from 0 through 255.400.\d{1.3}\.3}\._%+-]+@[A-Z0-9. is appended to the number.com.4}\b" True # Reading data matching the pattern from raw text: $matches Name Value -------0
[email protected]" $ip -match "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (\.\d{1. The following subexpression defines every single number: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) The construct ?: is optional and enhances speed. They can also filter data matching certain patterns from text. You have learned to create sub-expressions (by using parentheses) and how to iterate sub-expressions (by indicating the number of iterations in braces after the subexpression). After it come three alternatively permitted number formats separated by the alternation construct "|". A dot always follows the first three numbers. So.){3} A dot.){3}(?:25[05]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b" False The expression validates only expressions running into word boundaries (the anchor is \b).3}\b" True Finding Information in Text Regular expressions can recognize patterns.3}\b" True $ip -match "\b(?:\d{1. $rawtext = "If it interests you.com $matches[0]
[email protected]. For this reason. The quantifier "?" ensures that the preceding pattern must be included." # Simple pattern recognition: $rawtext -match "\b[A-Z0-9.).){3}\d{1.$ip = "300. 25[0-5] is a number from 250 through 255. Finally. When the fourth number is also appended.3}\. the regular expression is complete.[A-Z]{2. [01]?[0-9][0-9]? is a number from 0-9 or 00-99 or 100-199. regular expressions are perfect for parsing raw data.com . An IP address consists of four such numbers.500. de.de Searching for Several Keywords You can use the alternation construct "|" to search for a group of keywords.com found: spam@junk. and then find out which keyword was actually found in the string: "Set a=1" -match "Get|GetValue|Set|SetValue" True ._%+-]+@[A-Z0-9.com Groups Success Captures Index Length Value : : : : : : {
[email protected]} True {spam@junk.[A-Z]{2. So. you have to switch over to the RegEx object underlying the –match operator and use it directly._%+-]+@[A-Z0-9.4}\b" True $matches Name Value -------0
[email protected]]+\.de # Continue processing e-mail addresses: $regex.Matches($rawtext) | Select-Object -Property Value Value
[email protected]} Index : 4 Length : 13 Value :
[email protected]($rawtext) | ForEach-Object { "found: $($_.Does that also work for more than one e-mail addresses in text? Unfortunately.com} Success : True Captures : {test@test. if you want to find more than one occurrence of a pattern in raw text.Matches($rawtext) Groups : {test@test. The –match operator finds only the first matching expression.-]+\. # A raw text contains several e-mail addresses.[A-Z]{2. Since the RegEx object is case-sensitive by default. no.com # A RegEx object can find any pattern but is case sensitive by default: $regex = [regex]"(?i)\b[A-Z0-9.4}\b" $regex. –match finds the first one only: $rawtext = "test@test. put the "(?i)" option before the regular expression to make it work like -match.com
[email protected]} 42 13
[email protected] sent an e-mail that was forwarded to spam@junk." $rawtext -match "\b[A-Z0-9.de # Limit result to e-mail addresses: $regex.Value)" } found: test@test. the result would be incorrect: "SetValue a=1" -match "Get|GetValue|Set|SetValue" True $matches[0] Set Either change the order of keywords so that longer keywords are checked before shorter ones …: "SetValue a=1" -match "GetValue|Get|SetValue|Set" True $matches[0] SetValue … or make sure that your regular expression is precisely formulated. Insert word boundaries into your regular expression so that sequential order no longer plays a role: "SetValue a=1" -match "\b(Get|GetValue|Set|SetValue)\b" True $matches[0] SetValue It's true here. But note the order of keywords in your regular expression—it's crucial because the first matching keyword is the one selected. If your raw text has several occurrences of the keyword. GetValue a. use a RegEx object again: $regex = [regex]"\b(Get|GetValue|Set|SetValue)\b" $regex. GetValue} True {GetValue} 9 8 GetValue Groups : {SetValue.Matches("Set a=1.$matches Name ---0 Value ----Set $matches tells you which keyword actually occurs in the string. too. that -match finds only the first match. In this example. SetValue} Success : True Captures : {SetValue} . and remember that you're actually searching for single words. Set} Success : True Captures : {Set} Index : 0 Length : 3 Value : Set Groups Success Captures Index Length Value : : : : : : {GetValue. SetValue b=12") Groups : {Set. Sub-expressions defined in parentheses follow in additional elements. $matches will contain the entire searched pattern in the first array element named "0".*)" # Generate example line with tab character $line = "12/01/2009`tDescription" # Use regular expression to parse line: $line -match $pattern True # Show result: $matches Name ---2 1 0 $matches[1] 12/01/2009 $matches[2] Description Value ----Description 12/01/2009 12/01/2009 Description When you use sub-expressions. then text.*)\t(. For example. if a text line contains a date first. and if both are separated by tabs.Index Length Value : 21 : 8 : SetValue Forming Groups A raw text line is often a heaping trove of useful data. you could describe the pattern like this: # Defining pattern: two characters separated by a tab $pattern = "(. You can use parentheses to collect this data in subexpressions so that it can be evaluated separately later. To make them easier to read and understand.*)\t(?<Text>. you can assign sub-expressions their own names and later use the names to call results. To assign names to a sub-expression. type ? in parentheses for the first statement: # Assign subexpressions their own names: $pattern = "(?<Date>.*)" # Generate example line with tab character: $line = "12/01/2009`tDescription" # Use a regular expression to parse line: $line -match $pattern True # Show result: $matches Name ---- Value ----- . The basic principle is that all the data that you want to find in a pattern should be wrapped in parentheses because $matches will return the results of these sub-expressions as independent elements. . other times the long form of the month name is used. the regular expression is "greedy" and returns the longest possible match. the entire (detailed) text is reported back: February. As you've seen. because sub-expressions allow parts of a keyword to be declared optional: "Feb" -match "Feb(ruary)?" True $matches[0] Feb "February" -match "Feb(ruary)?" True $matches[0] February In both cases. but the months are not all specified in the same way. If they do. the regular expression recognizes the month. type "?:" as the first statement in your sub-expression: # Don't return a result for the second subexpression: $pattern = "(?<Date>. but returns different results in $matches. By default. If the text is "February.Text Description Description 12/01/2009 12/01/2009 Description Each result retrieved by $matches for each sub-expression naturally requires storage space.*)" # Generate example line with tab character: $line = "12/01/2009`tDescription" # Use a regular expression to parse line: $line -match $pattern True # No more results will be returned for the second subexpression: $matches Name Value -------Date 12/01/2009 0 12/01/2009 Description Greedy or Lazy? Shortest or Longest Possible Result Assume that you would like to evaluate month specifications in a logging file.*)\t(?:.Date 12/01/2009 $matches. discard them to increase the speed of your regular expression. If you don't need the results. Sometimes you use the short form." then the expression will search for a match starting with "Feb" and then continue searching "greedily" to check whether even more characters match the pattern. that's no problem for regular expressions.Text Date 0 $matches. To do so. 6}?end\b" True $matches[0] Name Value -------0 start to end Replacing a String You already know how to replace a string because you know the string –replace operator. "Feb(ruary)??" now stands for a pattern that starts with "Feb". shows how you can use the elements listed in Table 13. With regular expressions. you'd have to replace each term separately. "Martina" Hello. "Feb" -match "Feb(ruary)??" True $matches[0] Feb "February" -match "Feb(ruary)??" True $matches[0] Feb Finding String Segments Our last example. . followed by zero or one occurance of "ruary" (Quantifier "?").If your main concern is just standardizing the names of months. the regular expression will retrieve the text segment between the two words if at least one word is. "Our client" Our client Miller and Our client Meyer You can type any term in parentheses and use the "|" symbol to separate them. Martina But simple replacement isn't always sufficient.)".|Mrs. you would probably prefer getting back the shortest possible text: Feb. and not more than six other words are. Miller and Mrs. This example shows how complex (and powerful) regular expressions can get. in between the two words. Simply tell the operator what term you want to replace in a string: "Hello. All the terms will be replaced with the replacement string you specify. you should grab yourself a book on regular expressions and dive deeper: "Find word segments from start to end" -match "\bstart\W+(?:\w+\W+){1. simply use the alternation operator.11 to easily gather surprising search results. Some of the following examples show how that could be useful. so you can also use regular expressions for replacements. If you think that's cool. which locates text segments. and returning only the shortest possible match (which is turned on by the second "?"). Without regular expressions. Meyer" -replace "(Mr. add "?" to the expression. If you type two words. Let's say you'd like to replace several different terms in a string with one other term. Ralph" -replace "Ralph". "|": "Mr. To switch regular expressions to work lazy (returning the shortest possible match). "Our client $2" Our client . Meyer and Mr. usually the text of the old e-mail is quoted in your new e-mail and marked with ">" at the beginning of each line.|Mrs. 'Our client $2' Our client Miller.|Mrs. Mrs. Often. but sometimes you don't want to replace a keyword everywhere it occurs but only when it occurs in a certain context. that's sufficient. Werner" wasn't replaced. Werner" -replace "(Mr. Regular expressions can do the marking. Whenever you use parentheses in your regular expression.)\s*(Miller|Meyer)". if you put the replacement string inside double quotes. At least the name of the person should be retained. Miller. Our client Meyer and Mr. $ can also be masked by `$: "Mr. you could also use "$3" and so on for additional sub-expressions). Meyer. Our client and Mr. the result inside the parentheses is evaluated separately. Meyer and Mr. The term "Mr. For example. Use single quotation marks instead. . Werner" -replace "(Mr. Mrs. but the pattern you're looking for was correctly identified. Our client Meyer and Mr. Werner The back references don't seem to work." was found in the string.)\s*(Miller|Meyer)". Werner The result looks a little peculiar. and you can use these separate results in your replacement string. Miller. Can you see why? "$1" and "$2" look like PowerShell variables. Mrs.)\s*(Miller|Meyer)". As a result. but in reality they are part of the regular expression. Mrs.|Mrs. which is probably undefined. "Mr. Werner" -replace "(Mr." or a "Mrs. Werner # Alternatively. the result also shows that it doesn't make any sense here to replace the entire pattern. Our client and Mr. PowerShell replaces "$2" with the PowerShell variable $2. The terms "$1" and "$2" provide you the sub-expressions in the replacement string (the number is consequently a sequential number. Unfortunately. or Mrs.)\s*(Miller|Meyer)". "Our client" Our client. In such cases. Werner" -replace "(Mr. Meyer and Mr. Is that possible? This is where the back referencing you've already seen comes into play. The only replacements were Mr. The first sub-expression always reports whether a "Mr.Using Back References This last example replaces specified keywords anywhere in a string. Miller. when you respond to an e-mail. the context must be defined in some way in the pattern. The second sub-expression returns the name of the person. Miller and Mr. How could you change the regular expression so that it replaces only the names Miller and Meyer? Like this: "Mr. Werner Putting Characters First at Line Beginnings Replacements can also be made in multiple instances in text of several lines.|Mrs. "Our client `$2" Our client Miller. or add a backtick to the "$" special character so that PowerShell won't recognize it as its own variable and replace it: # Replacement text must be inside single quotation marks so that the PS variable $2: "Mr. Meyer and Mr. or Mrs. Miller. "^". >> I want to attach this text to an e-mail as a quote. That's why I would put a ">" before every line. and the "^" anchor represents the text beginning and the "$" the text ending. > I want to attach this text to an e-mail as a quote. [Text. I want to attach this text to an e-mail as a quote. > That's why I would put a ">" before every line. # only the first line is replaced: $text -replace "^". "\A". replacement will work in every line: $text -replace "(?m)^". # In multiline mode. That's why I would put a ">" before every line. # where the multiline option must be specified: [regex]::Replace($text. I want to attach this text to an e-mail as a quote. For this reason. this mode is turned off.However. the multi-line mode must be turned on with the "(?m)" statement. "> ". "\A" will continue to indicate the text beginning. the anchors "^" and "\A". Once the multi-line mode is turned on. "> " > Here is a little text. >> That's why I would put a ">" before every line. while "$" will mark the line ending. Normally.RegExOptions]::Multiline) > Here is a little text. while "^" will mark the line ending. [Text. That's why I would put a ">" before every line. to accomplish this. will suddenly behave differently. "> " > Here is a little text. # If you turn on multiline mode. So that these two anchors refer respectively to the line beginning and line ending of a text of several lines. as well as "$" and "\Z".RegularExpressions. "> ". # Using Here-String to create a text of several lines: $text = @" >> Here is a little text. "\Z" will indicate the text ending. # Normally. > I want to attach this text to an e-mail as a quote. Only then will –replace substitute the pattern in every single line.RegularExpressions. # The same can also be accomplished by using a RegEx object. >> "@ >> $text Here is a little text. > That's why I would put a ">" before every line. I want to attach this text to an e-mail as a quote. -replace doesn't work in multiline mode. Removing White Space . you need to know a little more about "multi-line" mode.RegExOptions]::Multiline) > Here is a little text. \A stands for the text beginning and ^ for the line beginning: [regex]::Replace($text. }". use the Read-Host cmdlet. PowerShell will replace PowerShell variables and special characters in the text.}\b". quantifier "{1. you can use back referencing again. A blank character follows (the character "\s" and quantifier "?"). Also.}"). The simplest way to describe patterns is to use the simple wildcards in Table 13. that is.}"). "Too many blank characters" -replace "\s{2. only support very basic pattern recognition.Regular expressions can perform routine tasks as well. . PowerShell stores text in string objects. which start with @"(Enter) and end with "@(Enter). must occur at least once (at least one and any number of iterations of the word. Regular expressions precisely identify even complex patterns and can be used with the operators -match or –replace. This pattern. you can compose formatted text. They consist of very specific placeholders. the blank character and the repeated word. the first located word. '$1' This is a test Summary Text is defined either by single or double quotation marks. The pattern describes a blank character (char: "\s") that occurs at least twice (quantifier: "{2. which support methods to work on the stored text. while easy to use.5).11. A far more sophisticated tool are regular expressions.7.6). Along with the formatting operator. By using the format operator –f. Along with the dynamic methods that always refer to text stored in a string object. there are also static methods that are provided directly by the string data type by qualifying the string object with "[string]::". # Find and remove doubled words in a text: "This this this is a test" -replace "\b(\w+)(\s+\1){1. If you use double quotation marks. The entire pattern is then replaced with the first back reference.2). " " Too many blank characters Finding and Removing Doubled Words How is it possible to find and remove doubled words in text? Here. PowerShell has a number of string operators you can use to validate patterns or to replace a string (Table 13. Text enclosed in single quotation marks remains asis. That is replaced with a normal blank character. quantifiers and anchors listed in Table 13. Use the . You can use these methods by typing a dot after the string object (or the variable in which the text is stored) and then activating auto complete (Table 13. Multi-line text can be defined with HereStrings. It consists of one word (the character "\w" and quantifier "+"). simple wildcard patterns can only recognize the patterns.}\b" The pattern searched for is a word (anchor: "\b"). Simple wildcard patterns.NET object [regex] if you want to match multiple pattern instances. If you want to prompt the user for input text.3 through Table 13. such as remove superfluous white space. The pattern could be described as follows: "\b(\w+)(\s+\1){1. This gives you the option to display text in different ways or to set fixed widths to output text in aligned columns (Table 13. they cannot extract data from them. PowerShell takes this into account and makes working with XML data much easier than before.</staff> If a node has no particular content. The end tag is preceded by "/". <staff branch="Hanover" Type="sales"> <employee> <Name>Tobias Weltner</Name> <function>management</function> <age>39</age> </employee> <employee> <Name>Cofi Heidecke</Name> <function>security</function> <age>4</age> </employee> </staff> The XML data is wrapped in an XML node which is the top node of the document: . If the branch office in Hanover doesn't have any staff currently working in the field. a piece of information is delimited by a start and end tag. Topics Covered: Taking a Look At XML Structure Loading and Processing XML Files o Accessing Single Nodes and Modifying Data o Using SelectNodes() to Choose Nodes o Accessing Attributes o Adding New Nodes Exloring the Extended Type System o The XML Data of the Extended Type System o Finding Pre-Defined Views Taking a Look At XML Structure XML uses tags to uniquely identify pieces of information. the tag could look like this: <staff branch="Hanover" Type="sales"/> The following XML structure describes two staff members of the Hanover branch office who are working in the sales department. Typically.In today‘s world. its start and end tags can be combined. and in the next example. and the ending symbol "/" drifts toward the end of the tag. the node is called "Name": <Name>Tobias Weltner</Name> Nodes can be decorated with attributes.. data is no longer presented in plain -text files. the result is called a "node". A tag is a pair of angle brackets like the ones used for HTML documents. Attributes are stored in the start tag of the node like this: <staff branch="Hanover" Type="sales">. XML (Extensible Markup Language) has evolved to become a de facto standard because it allows data to be stored in a flexible yet standard way. Instead.. <?xml version="1.Load("$env:temp\employee.xml) A faster approach uses a blank XML object and its Load() method: $xmldata = New-Object XML $xmldata. which is a formal description of the structure of that XML file. it is easy to read its content because PowerShell automatically turns XML nodes and attributes into object properties. you can easily create them using any editor or directly from within PowerShell. This line would read the content from a file $env:temp\employee. Because XML files consist of plain text. try this: $xmldata.staff. Once the XML data is stored in an XML object. The schema could. specify that there must always be a node called "staff" as part of staff information. to read the staff from the sample XML data.employee .0" ?> This particular header contains a version attribute which declares that the XML structure conforms to the specifications of XML version 1.0. Let's save the previous staff list as an xml file: $xml = @' <?xml version="1. you can either convert the text to the XML data type.xml and convert it to XML: $xmldata = [xml](Get-Content $env:temp\employee. There can be additional attributes in the XML header. or you can instantiate a blank XML object and load the XML from a file or a URL in the Internet. The schema would also specify that information relating to name and function must also be defined for each staff member.xml") Conversion or loading XML from a file of course only works when the XML is valid and contains no syntactic errors. Else. So. Often you find a reference to a "schema". which in turn could include as many sub-nodes named "staff" as required. the conversion will throw an exception.xml XML is case-sensitive! Loading and Processing XML Files To read and evaluate XML.0" standalone="yes"?> <staff branch="Hanover" Type="sales"> <employee> <Name>Tobias Weltner</Name> <function>management</function> <age>39</age> </employee> <employee> <Name>Cofi Heidecke</Name> <function>security</function> <age>4</age> </employee> </staff> '@ | Out-File $env:temp\employee. for example. to get to the employee data below the staff node.employee | Where-Object { $_. you can use the PowerShell pipeline and Where-Object.Name Age -------Tobias Weltner 39 Cofi Heidecke 4 function ----management security Accessing Single Nodes and Modifying Data To pick out a specific node from a set of nodes.employee | Where-Object { $_.Name -match "Tobias Weltner" } Name function Age -----------Tobias Weltner management 39 $employee = $xmldata.staff.employee Name function Age -----------Tobias Weltner vacation 39 Cofi Heidecke security 4 If you want to save changes you applied to XML data. call the Save() method: $xmldata.xml") Using SelectNodes() to Choose Nodes Another way of picking nodes is to use the method SelectNode() and its so-called XPath query language. $xmldata.staff. So. use this approach: $xmldata.staff.SelectNodes('staff/employee') Name Age -------Tobias Weltner 39 function ----management . you can not only read data but also change it. This would pick out a particular employee from the list of staff. As you will see.function = "vacation" $xmldata.Name -match "Tobias Weltner" } $employee.Save("$env:temp\updateddata. CreateNavigator() # Output the last employee name of the Hanover branch office: $query = "/staff[@branch='Hanover']/employee[last()]/Name" $navigator.XML.Cofi Heidecke 4 security The result is pretty much the same as before.IO.IO.SelectNodes('staff/employee[age<18]') Name function Age -----------Cofi Heidecke security 4 To the last employee on the list.StringReader ]` (Get-Content $env:temp\employee.SelectNodes('staff/employee[1]') Name Age -------Tobias Weltner 39 function ----management If you'd like.XPath.xml | Out-String) $navigator = $xpath. you can also use an XpathNavigator: # Create navigator for XML: $xpath = [System. but XPath is very flexible and supports wildcards and additional control. you can get a list of all employees who are under the age of 18: $xmldata. The next statement retrieves just the first employee node: $xmldata.Select($query) | Format-Table Value Value ----Cofi Heidecke # Output all employees of the Hanover branch office except for Tobias Weltner: $query = "/staff[@branch='Hanover']/employee[Name!='Tobias Weltner']" $navigator.XPathDocument][System.SelectNodes('staff/employee[position()>1]') Alternatively.Select($query) | Format-Table Value Value . use this approach: $xmldata.SelectNodes('staff/employee[last()]') $xmldata.TextReader][System. staff.staff.SetAttribute("branch".CreateElement("employee") $newemployee. use Attributes: $xmldata.AppendChild($newemployee) # Check result: $xmldata.----Cofi Heidecke Accessing Attributes Attributes are pieces of information that describe an XML node.staff. use CreateElement() to create an employee element and then fill in the data.GetAttribute("branch") Hanover Use SetAttribute() to specify new attributes or modify (overwrite) existing ones: $xmldata. If you'd like to read the attributes of a node.staff. Finally.employee Name Age -------Tobias Weltner 39 Cofi Heidecke 4 Bernd Seiler function ----management security expert .InnerXML = '<Name>Bernd Seiler</Name><function>expert</function>' # Write nodes in XML: $xmldata.GetAttribute("branch") New York Adding New Nodes If you'd like to add new employees to your XML.staff. "New York") $xmldata.staff.Attributes #text ----Hanover sales Use GetAttribute() if you'd like to query a particular attribute: $xmldata. add the element to the XML: # Create new node: $newemployee = $xmldata. AssemblyName ViewSelectedBy TableControl System.Diagnostics..PrintDo.get_InnerXml() <?xml version="1. The XML Data of the Extended Type System Whenever PowerShell needs to convert an object into text. [xml]$file = Get-Content "$pshome\dotnettypes.Assembly ViewSelectedBy TableControl System.EventLog ViewSelectedBy TableControl System.ps1xml All these files define a multitude of Views. which you can examine using PowerShell XML support.format. Type-format files control which additional properties and methods should be added to objects.Diagnostics.FileVersionInfo ViewSelectedBy TableControl System. There are format-files and type-files.EventLogEntry ViewSelectedBy TableControl System. The ETS is responsible for turning objects into readable text.View Name ViewSelectedBy TableControl --------------------System.Globalization. With the basic knowledge about XML that you gained so far.ps1xml".ViewDefinitions.Reflection. too.CultureInfo ViewSelectedBy TableControl System.ps1xml" $file.. Format-files control which object properties are shown and how the object structure is represented.Version ViewSelectedBy TableControl System.Diagnostics.Configuration. you can start exploring the ETS XML files and learn more about the inner workings of PowerShell.0"?><Branch office staff="Hanover" Type="sales"><employee> <Name>Tobias Weltner</Name><function>management</function><age>39</age> </employee><employee><Name>Cofi Heidecke</Name><function>security</function> <age>4</age></employee><employee><Name>Bernd Seiler</Name><function> expert</function></employee></staff> Exploring the Extended Type System The PowerShell Extended Type System (ETS) is XML-based.Reflection. PowerShell comes with a set of xml files that all carry the extension ". This database really is a collection of XML files in the PowerShell root folder $pshome: Dir $pshome\*.Printing. ViewSelectedBy TableControl ------- . it searches through its internal "database" to find information about how to best format and display the object.Drawing.# Output plain text: $xmldata.format. Process System.DirectoryServices.ViewDefinitions. Get-Process | Format-Table -View Priority Get-Process | Format-Table -View StartTime To find out which views exist. Expression= {$_.Management..Collections.Diagnostics. $view | Where-Object { $_. take a look into the format.Diagnostics.DictionaryEntry System.ps1xml files that describe the object type.DirectoryEntry PSSnapInInfo System.Configuration.) ViewSelectedBy ViewSelectedBy ViewSelectedBy ViewSelectedBy ViewSelectedBy ViewSelectedBy ViewSelectedBy ViewSelectedBy Finding Pre-Defined Views Pre-defined views are interesting because you can use the -View parameter to change the way PowerShell presents results with the cmdlets Format-Table or Format-List.Automation.Process System. ObjectType } | Sort-Object ObjectType Name ---Dictionary DateTime Priority StartTime process process ProcessModule DirectoryEntry System. [xml]$file = Get-Content "$pshome\dotnettypes.Process System.View | Select-Object Name..ServiceProcess.PSSnapI.TypeName}} $file..Automation.Dictionary TableControl ProcessModule TableControl process TableControl PSSnapInInfo PSSnapInInfo TableControl Priority TableControl StartTime TableControl service TableControl (. service System.Diagnostics.Diagnostics.ViewSelectedBy.Process System.Name -ne $_.ServiceController ObjectType ---------System.PSSnapI. PSSnapInInfo System..Management..format.Diagnostics.DateTime System.ps1xml" $view = @{ Name='ObjectType'.ProcessModule .. Autom...Security.. you'll learn how to use PowerShell cmdlets to automate the most common file system tasks.Diagnostics.ViewSelectedBy.TimeSpan System.TimeSpan ObjectType ---------System...Autom.ListControl) { "List" } elseif ($_..Process System..Process System.Here you see all views defined in this XML file. System. You'll only get a complete list of all view definitions when you generate a list for all of these files.ServiceProcess.Diagnostics.Diagnostics.ServiceProcess.Reflection. System.TimeSpan System. System... System. are on that list.Even. System.Assembly System.Diagnostics.File.Even. System... Priority process StartTime process PSSnapInInfo PSSnapInInfo System.format.Diagnostics..Diagnostics. though.Count gt 1} | ForEach-Object { $_...Collections.Diagnostics.Process System.AccessC..AccessC.Even. if you are comfortable with commands like "dir" or "ls" to list folder content.Diagnostics. System.File. System.Reflection..Security..TableControl) { "Table" } elseif ($_. System.TimeSpan System.. Since they are just aliases .Collections..Diagnostics.ViewDefinitions.Diagnostics....TypeName}} $type = @{ Name='Type'..Reflection...S.TimeSpan Type ---Table List List Table Table List List Table Table Wide Table Table Table List Table List List Table Table List Wide Table List Remember there are many format. So.AccessC.they do not necessarily work exactly the same anymore. System.Assembly System.CustomControl) { "Custom" }}} $file.references to PowerShell‘s own cmdlets . System.. the list just shows views that use Table format. expression={if ($_. you can still use them.ServiceProcess. which we just used... System.Group} Name ---Dictionary System. System.Dict.Security.ps1xml" $view = @{ Name='ObjectType'. To get a complete list of all views. In this chapter.Assembly System. $view..ps1xml-files containing formatting information.Diagnostics..S. System.Management. System. The object types for which the views are defined are listed in the second column..Reflection. System.. service System. System.Configuration..File.Diagnostics.Even.. here is a more sophisticated example: [xml]$file = Get-Content "$pshome\dotnettypes.. Working with files and folders is traditionally one of the most popular areas for administrators.TimeSpan System.WideControl) { "Wide" } elseif ($_. The Priority and StartTime views.Dict.File..Diagnostics.. However.Assembly System... $type | Sort-Object ObjectType | Group-Object ObjectType | Where-Object { $_..Even.Collections.. Expression= {$_. System.Diagnostics.S. System. System.Process System. Topics Covered: ..Diagnostics..AccessC. System.Dict.Even...Management..Diagnostics.Even. System. PowerShell eases transition from classic shell commands with the help of a set of predefined "historic" aliases and functions.Security..Even..View | Select-Object Name. Functions. That is so because PowerShell calls everything "item" that lives on a drive. or Scripts o Selecting Files or Folders Only Navigating the File System o Relative and Absolute Paths o Converting Relative Paths into Absolute Paths o Pushing and Popping Directory Locations o Special Directories and System Paths o Constructing Paths Working with Files and Directories o Creating New Directories o Creating New Files o Reading the Contents of Text Files o Processing Comma-Separated Lists o Moving and Copying Files and Directories o Renaming Files and Directories o Bulk Renames o Deleting Files and Directories o Deleting Directory Contents o Deleting Directories Plus Content Getting to Know Your Tools One of the best ways to get to know your set of file system-related PowerShell cmdlets is to simply list all aliases that point to cmdlets with the keyword "item" in their noun part. Getting to Know Your Tools Accessing Files and Directories o Listing Folder Contents o Choosing the Right Parameters o Getting File and Directory Items o Passing Files to Cmdlets. PS> Get-Alias -Definition *-item* CommandType ----------Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Name ---cli clp copy cp cpi cpp del erase gi gp ii mi move mp mv ni rd ren ri rm ModuleName ---------Definition ---------Clear-Item Clear-ItemProperty Copy-Item Copy-Item Copy-Item Copy-ItemProperty Remove-Item Remove-Item Get-Item Get-ItemProperty Invoke-Item Move-Item Move-Item Move-ItemProperty Move-Item New-Item Remove-Item Rename-Item Remove-Item Remove-Item . Accessing Files and Directories Use Get-ChildItem to list the contents of a folder. split paths into parent and child.. typically. add the switch parameter -Recurse: PS> Get-ChildItem -Path $home -Filter *.. .. Omit a path only when you use PowerShell interactively and know where your current location actually is. Definition ---------. They all use the noun "Path". To find script files recursively (searching through all child folders).. PS> Get-Command -Noun path CommandType ----------Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Name ---Convert-Path Join-Path Resolve-Path Split-Path Test-Path ModuleName ---------Microsoft. Try searching for other file types. or scripts Listing Folder Contents If you don't specify a path.. . Microsoft. functions.. Get-ChildItem handles a number of important file system-related tasks: Searching the file system recursively and finding files Listing hidden files Accessing files and directory objects Passing files to other cmdlets.. This line will get all Microsoft Word documents in your profile: . There are two historic aliases: Dir and ls. your own files are not stored in the root of your profile folder.PowerSh.... Time to put Get-ChildItem to work: to get a list of all PowerShell script files stored in your profile folder.. Get-ChildItem lists the contents of the current directory.. it is risky to use Get-Childitem in scripts without specifying a path. then maybe you did not create any PowerShell script file yet..ps1 -Recurse This may take much longer. If you still get no result. and you can use these cmdlets to construct paths. try this: PS> Get-ChildItem -Path $home -Filter *..PowerSh. resolve paths or check whether files or folders exist. Microsoft.PowerSh..Alias Alias Alias ItemProperty Alias ItemProperty Alias Alias rmdir rni rnp rp si sp Remove-Item Rename-Item RenameRemoveSet-Item Set-ItemProperty In addition.PowerSh. . Microsoft.. Microsoft.. ... Since the current directory can vary.PowerSh. PowerShell provides a set of cmdlets that help dealing with path names..ps1 Most likely. this will not return anything because. so you could search multiple drives or folders in one line. use a pipeline and send the results to Select-Object to only select the content of the FullName property: PS> Get-ChildItem -Path $env:windir | Select-Object -ExpandProperty FullName Some characters have special meaning to PowerShell. or use its short form -ea 0: PS> Get-ChildItem -Path $home -Filter *. If you wanted to select items on Registry drives like HKLM:\ or HKCU:\. That's why -Filter is fast and efficient. no matter which provider is implementing that drive. -Filter is implemented by the underlying drive provider. the drive provider must support it. such as square brackets or wildcards such as '*'. To hide such error messages.log -Recurse -ea 0 If you just need the names of items in one directory.log-files on drives C:\ and D:\ (and takes a long time because of the vast number of folders it searches): PS> Get-ChildItem c:\.ps1 Recurse}). and only then does -Include filter out the items you want. -Filter currently only works for file system drives.PS> Get-ChildItem -Path $home -Filter *. PS> (Measure-Command {Get-ChildItem $home -Filter *. though. If you want PowerShell to ignore special characters in path names and instead take the path literally. use the -LiteralPath parameter instead of -Path.TotalSeconds 28. Choosing the Right Parameters In addition to -Filter. This would find all . you may run into situations where you do not have access to a particular subfolder. there is a parameter that seems to work very similar: -Include: PS> Get-ChildItem $home -Include *.1017376 You also see functional differences because -Include only works right when you also use the -Recurse parameter. add the common parameter -Erroraction SilentlyContinue which is present in all cmdlets. This is slower but universal. so it is retrieving only those files and folders that match the criteria in the first place.ps1 -Recurse You'll see some dramatic speed differences. -Include on the contrary is implemented by PowerShell and thus is independent of provider implementations. Get-ChildItem then raises an exception but continues its search. The reason for these differences is the way these parameters work.TotalSeconds 4.ps1 Recurse}). The provider returns all items. To be able to use -Filter.doc* -Recurse -ea 0 The -Path parameter accepts multiple comma-separated values.6830099 PS> (Measure-Command {Get-ChildItem $home -Include *. It works on all drives. though: -Filter works significantly faster than -Include. you must use -Include. .doc* -Recurse When searching folders recursively. d:\ -Filter *. use the parameter -Name: PS> Get-ChildItem -Path $env:windir -Name To list only the full path of files. use this code .-Include has some advantages. to find the largest files in your profile. pipe the result to Measure-Object: PS> Get-ChildItem $env:windir -Recurse -Include *. For example. *. use Get-Item: PS> Get-Item $env:windir\explorer.jpg.Core\FileSystem::C:\Windows\explorer.bmp. so to get the properties of an individual file or folder.ps1 -Recurse The counterpart to -Include is -Exclude.log-files in your windows folder: PS> Get-ChildItem $env:windir -Filter *.gif -ea 0 | >> Measure-Object | Select-Object -ExpandProperty Count >> 6386 You can also use Measure-Object to count the total folder size or the size of selected files.*.exe .*. which enable you to get a list of all image files in your profile or the windows folder: Get-Childitem -Path $home.length -gt 100MB } If you want to count files or folders.log -ea 0 | Measure-Object -Property Length -Sum | >> Select-Object -ExpandProperty Sum Getting File and Directory Items Everything on a drive is called "Item".png. It understands advanced wildcards and supports multiple search criteria: # -Filter looks for all files that begin with "[A-F]" and finds none: PS> Get-ChildItem $home -Filter [a-f]*. use Where-Object (Chapter 5).PowerShell.ps1 -Recurse # -Include understands advanced wildcards and looks for files that begin with a-f and # end with .jpg. the Include and -Exclude parameters accept arrays.png.PowerShell. too. Unlike -Filter. Use -Exclude if you would like to suppress certain files.it finds all files larger than 100MB: PS> Get-ChildItem $home -Recurse | Where-Object { $_.gif -ea 0 If you want to filter results returned by Get-ChildItem based on criteria other than file name.ps1: PS> Get-ChildItem $home -Include [a-f]*.Core\FileSystem::C:\Windows PSChildName : explorer.exe | Select-Object * PSPath : Microsoft. *.*.*. This line will count the total size of all .bmp. $env:windir -Recurse -Include *.e xe PSParentPath : Microsoft. Finally. you have the proper permissions.CreationTime = '1812/4/11 09:22:11' Explorer $env:temp This will create a test file in your temporary folder.txt $file = Get-Item $env:temp\testfile. Amazing.04.04. isn't it? Passing Files to Cmdlets.MUI FileVersion: 6. across multiple folders or even drives. Functions.2011 17:02:33 27.EXE.txt $file.exe InternalName: explorer OriginalFilename: EXPLORER.16385 Debug: False Patched: False PreRelease: False PrivateBuild: False SpecialBuild: False Language: English (United States) explorer -a--explorer.02.1.Core\FileSystem False File: C:\Windows\explorer. Get-ChildItem can pass these objects to other cmdlets or to your own functions and scripts.PowerShell.exe 27.exe .2011 07:19:30 25.04.7600.04.1.16385 (win7_rtm.2011 06:19:30 Archive BaseName Mode Name Length DirectoryName Directory IsReadOnly Exists FullName Extension CreationTime CreationTimeUtc LastAccessTime LastAccessTimeUtc LastWriteTime LastWriteTimeUtc Attributes : : : : : : : : : : : : : : : : : You can even change item properties provided the file or folder is not in use.exe 2871808 C:\Windows C:\Windows False True C:\Windows\explorer. Take a look at this piece of code: "Hello" > $env:temp\testfile.7600. or Scripts Because Get-ChildItem returns individual file and folder objects.2011 15:02:33 27. read its creation time and then changes the creation time to November 4. explorer opens the temporary file so you can right-click the test file and open its properties to verify the new creation time. This makes Get-ChildItem an important selection command which you can use to recursively find all the files you may be looking for.2011 17:02:33 27. and the property allows write access.CreationTime $file.2011 15:02:33 25.02.PSDrive PSProvider PSIsContainer VersionInfo : : : : C Microsoft.090713-1255) FileDescription: Windows Explorer Product: Microsoft® Windows® Operating System ProductVersion: 6. . 1812. 0. C:\Program Files\Common Files\Microsoft Selecting Files or Folders Only Because Get-ChildItem does not differentiate between files and folders.0. 2011: PS> Get-ChildItem $env:windir | Where-Object { $_. 2008.PSIsContainer } PS> Get-ChildItem | Where-Object { $_.641.0.1108.IO..2 2.dll) PS> $totallist = $list1 + $list2 PS> $totallist | Select-Object -ExpandProperty VersionInfo | Sort-Object Property FileName ProductVersion -------------3. Sh.641. or examine the mode property: # List directories only: PS> Get-ChildItem | Where-Object { $_ -is [System.. 1..IO. There are several ways to accomplish this.PSIsContainer -eq $false} PS> Get-ChildItem | Where-Object { $_. You can check the type of returned object. the next code snippet finds all jpg files in your Windows folder and copies them to a new folder: PS> New-Item -Path c:\WindowsPics -ItemType Directory -ea 0 PS> Get-ChildItem $env:windir -Filter *.) FileVersion ----------3. 1 FileName -------C:\Program Files\Bonjour\mdnsNSP. which PowerShell combines into a total list and sends on for further processing in the pipeline.Mode -notlike 'd*' } Where-Object can filter files according to other criteria as well.2 2. For example. (. version..For example.. 1.dll) PS> $list2 = @(Get-ChildItem $env:programfiles -Recurse -Filter *. 1 Sh. In the following example. You can also combine the results of several separate Get-ChildItem commands. and description of DLL files: PS> $list1 = @(Get-ChildItem $env:windir\system32\*. check the PowerShell PSIsContainer property.FileInfo] } PS> Get-ChildItem | Where-Object { $_.CreationTime -gt [datetime]::Parse("May 12.. use the following pipeline filter if you'd like to locate only files that were created after May 12. The example takes all the DLL files from the Windows system directory and all program installation directories..dll C:\Program Files\Common Files\Microsoft 2008.0..jpg -Recurse -ea 0 | >> Copy-Item -Destination c:\WindowsPics Get-ChildItem first retrieved the files and then handed them over to Copy-Item which copied the files to a new destination. two separate Get-ChildItem commands generate two separate file listings.0. and then returns a list with the name.Mode -like 'd*' } # List files only: PS> Get-ChildItem | Where-Object { $_ -is [System. 0. 2011") } .1108..DirectoryInfo] } PS> Get-ChildItem | Where-Object { $_. it may be important to limit the result of Get-ChildItem to only files or only folders.. CreationTime -gt (GetDate).. Absolute paths are always unique and are independent of your current directory. Likewise. so .AddDays(-14) } Navigating the File System Unless you changed your prompt (see Chapter 9).. Cd . Relative path specifications are useful. Relative path specifications depend on the current directory. for example. . Your work script will then be able to locate library scripts under relative paths —no matter what the directory is called.txt file in the parent directory. \ ~ Meaning Current directory Parent directory Root directory Home directory Example ii ..txt refers to the test. use Set-Location or the Cd alias: # One directory higher (relative): PS> Cd . # In the parent directory of the current drive (relative): PS> Cd \ # In a specified directory (absolute): PS> Cd c:\windows # Take directory name from environment variable (absolute): PS> Cd $env:windir # Take directory name from variable (absolute): PS> Cd $home Relative and Absolute Paths Paths can either be relative or absolute.\test.\test. . You can find out the current location by calling Get-Location: PS> Get-Location Path ---C:\Users\Tobias If you want to navigate to another location in the file system. the current directory is part of your input prompt.txt always refers to the test..You can use relative dates if all you want to see are files that have been changed in the last two weeks: PS> Get-ChildItem $env:windir | Where-Object { $_.txt file in the current directory. Character . when you want to use library scripts that are located in the same directory as your work script. Cd \ Cd ~ Result Opens the current directory in Windows Explorer Changes to the parent directory Changes to the topmost directory of a drive Changes to the directory that PowerShell initially creates automatically . you can use Pop-Location -Stack job1 to restore the initial directory from this stack.0\FileSystem.0\Certificate. it is bad practice to . PushLocation -Stack job1 puts the current directory not on the standard stack. to perform a task that forces you to temporarily leave your current directory. you can complete your task and when use Pop-Location to return to where you were before.ps1xml C:\Windows\System32\WindowsPowerShell\v1. Use Pop-Location to get it back again. PowerShell must convert these relative paths into absolute paths.txt Path ---C:\Users\Tobias Weltner\test. by using Resolve-Path. So.txt Be careful though: Resolve-Path only works for files that actually exist.txt.0\DotNetTypes. That occurs automatically when you submit a relative path to a cmdlet.format. too.format.ps1xml C:\Windows\System32\WindowsPowerShell\v1.0\PowerShellTrace.format. or your desktop. Since the exact location of these paths can vary depending on your installation setup.0\PowerShellCore. PS> Resolve-Path .ps1xml C:\Windows\System32\WindowsPowerShell\v1.0\Help. your user profile. This enables you to create as many stacks as you want.\test. Each Push-Location adds a new directory to the top of the stack. but on the stack called ―job1‖. The following call will retrieve the names of all ps1xml files in the PowerShell home directory: PS> Resolve-Path $PsHome\*. for example the Windows folder itself.0\Registry. first type Push-Location to store your current location.ps1xml C:\Windows\System32\WindowsPowerShell\v1. Also. Then.Table 15. Special Directories and System Paths There are many standard folders in Windows. Cd $home will always take you back to your home directory.format.format. Resolve-Path errors out.0\types. both Push-Location and Pop-Location support the -Stack parameter.ps1xml C:\Windows\System32\WindowsPowerShell\v1.ps1xml C:\Windows\System32\WindowsPowerShell\v1.format. such as one for each task.ps1xml Pushing and Popping Directory Locations The current directory can be ―pushed‖ onto a ―stack‖ by using Push-Location.format.2: Important special characters used for relative path specifications Converting Relative Paths into Absolute Paths Whenever you use relative paths. If there is no file in your current directory that's called test. Resolve-Path can also have more than one result if the path that you specify includes wildcard characters. You can resolve relative paths manually.ps1xml C:\Windows\System32\WindowsPowerShell\v1.ps1xml Path ---C:\Windows\System32\WindowsPowerShell\v1. exe.IconLocation = "notepad.} Definition ---------static System. PS> [Environment]::GetFolderPath("Desktop") C:\Users\Tobias Weltner\Desktop # Put a link on the Desktop: PS> $path = [Environment]::GetFolderPath("Desktop") + "\EditorStart.lnk" PS> $comobject = New-Object -ComObject WScript. Some are covered by the Windows environment variables.} Cookies Property Cookies {get.Environment class of the .Environment+SpecialFolder static System. The GetFolderPath() method of the System.} CommonApplicationData Property CommonApplicationData .Environment+SpecialFolder .Environment+SpecialFolder static System. you'll need the path to the Desktop which is missing in the list of environment variables.Save() To get a list of system folders known by GetFolderPath().Environment+SpecialFolder static System.Shell PS> $link = $comobject.Environment+SpecialFolder Name MemberType ------------ApplicationData Property ApplicationData {get. That's why it is important to understand where you can find the exact location of these folders..Environment+SpecialFolder] | Get-Member -Static -MemberType Property TypeName: System.0" PS> $link.exe" PS> $link. If you'd like to put a file directly on a user‘s Desktop. and others can be retrieved via .hard-code these paths into your scripts .NET methods. The following code illustrates how you can put a link on the Desktop.hardcoded system paths may run well on your machine and break on another.3: Important Windows directories that are stored in environment variables Environment variables cover only the most basic system paths. Special directory Application data User profile Data used in common Public directory Program directory Roaming Profiles Temporary files (private) Temporary files Windows directory Description Application data locally stored on the machine User directory Directory for data used by all programs Common directory of all local users Directory in which programs are installed Application data for roaming profiles Directory for temporary files of the user Directory for temporary files Directory in which Windows is installed Access $env:localappdata $env:userprofile $env:commonprogramfiles $env:public $env:programfiles $env:appdata $env:tmp $env:temp $env:windir Table 15. CommonProgramFiles Property CommonProgramFiles {get.NET framework (Chapter 6) can help.CreateShortcut($path) PS> $link. use this code snippet: PS> [System.targetpath = "notepad.. Environment+SpecialFolder static System.} History Property History {get.Environment+SpecialFolder And this would get you a list of all system folders covered plus their actual paths: PS> [System.} LocalApplicationData Property LocalApplicationData {.} SendTo Property SendTo {get.Environment+SpecialFolder] | Get-Member -Static -MemberType Property | >> ForEach-Object {"{0.Environment+SpecialFolder static System.Environment+SpecialFolder static System. [Environment]::GetFolderPath($_.-25}= {1}" -f $_.Name) } >> ApplicationData = C:\Users\Tobias Weltner\AppData\Roaming CommonApplicationData = C:\ProgramData CommonProgramFiles = C:\Program Files\Common Files Cookies = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\Cookies Desktop = C:\Users\Tobias Weltner\Desktop DesktopDirectory = C:\Users\Tobias Weltner\Desktop Favorites = C:\Users\Tobias Weltner\Favorites .Environment+SpecialFolder static System.} MyMusic Property MyMusic {get.Environment+SpecialFolder static System.} DesktopDirectory Property DesktopDirectory {get.} static System.Environment+SpecialFolder static System.} MyDocuments Property MyDocuments {get.} Startup Property Startup {get.} MyPictures Property MyPictures {get.} Programs Property Programs {get..Environment+SpecialFolder static System.} System Property System {get.Environment+SpecialFolder static System.Environment+SpecialFolder static System.} StartMenu Property StartMenu {get.Environment+SpecialFolder static System. MyComputer Property MyComputer {get.Environment+SpecialFolder static System.} Recent Property Recent {get.} Personal Property Personal {get.Environment+SpecialFolder static System.Environment+SpecialFolder static System.} InternetCache Property InternetCache {get.} Favorites Property Favorites {get.Environment+SpecialFolder static System.Environment+SpecialFolder static System.Environment+SpecialFolder static System.} Templates Property Templates {get.name.Environment+SpecialFolder static System.Desktop Property Desktop {get..} ProgramFiles Property ProgramFiles {get.Environment+SpecialFolder static System. it adds a bunch of new drives.Environment+SpecialFolder] | Get-Member -Static -MemberType Property | ForEach-Object { New-PSDrive -Name $_. Here it is: function Map-Profiles { [System. You can now easily take a look at your browser cookies or even get rid of them: PS> Get-ChildItem cookies: PS> Get-ChildItem cookies: | del -WhatIf You can check content of your desktop: PS> Get-ChildItem desktop: And if you'd like to see all the drives accessible to you.History = C:\Users\Tobias Weltner\AppData\Local\Microsoft\Windows\History InternetCache = C:\Users\Tobias Weltner\AppData\Local\Microsoft\Windows\Temporary Internet Files LocalApplicationData = C:\Users\Tobias Weltner\AppData\Local MyComputer = MyDocuments = C:\Users\Tobias Weltner\Documents MyMusic = C:\Users\Tobias Weltner\Music MyPictures = C:\Users\Tobias Weltner\Pictures Personal = C:\Users\Tobias Weltner\Documents ProgramFiles = C:\Program Files Programs = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\Start Menu\Programs Recent = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\Recent SendTo = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\SendTo StartMenu = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\Start Menu Startup = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\ Start Menu\Programs\Startup System = C:\Windows\system32 Templates = C:\Users\Tobias Weltner\AppData\Roaming\Microsoft\Windows\Templates You can use this to create a pretty useful function that maps drives to all important file locations.Name)) ` -Scope Global } } Map-Profiles When you run this function.Name -PSProvider FileSystem -Root ([Environment]::GetFolderPath($_. run this command: PS> Get-PSDrive . txt") .txt") PS> $path C:\Users\Tobias Weltner\Desktop\test.txt") GetFileNameWithoutExtension("c:\test\file.txt".txt" PS> $path C:\Users\Tobias Weltner\Desktop\file.NET framework methods: PS> $path = [System.txt") GetDirectoryName("c:\test\file. To put a file onto your desktop. Combine() corresponds to Join-Path Returns the directory.4. "test. "test.txt". for example: PS> [System.txt") GetExtension("c:\test\file. so you can set them up any way you like.Path]::ChangeExtension("test. make sure you add Map-Profiles and its call to your profile script: PS> if ((Test-Path $profile) -eq $false) { New-Item $profile -ItemType File Force } PS> Notepad $profile Constructing Paths Path names are plain-text. If you want to use them daily.txt A more robust way is using Join-Path because it keeps track of the backslashes: PS> $path = Join-Path ([Environment]::GetFolderPath("Desktop")) "test.txt" PS> $path C:\Users\Tobias Weltner\Desktop\test. you can use .IO.Path]::Combine([Environment]::GetFolderPath("Desktop").txt Or. GetDirectoryName() corresponds to Split-Path -parent GetExtension() Returns the file extension Returns the file name.txt The System.ps1 Method ChangeExtension() Description Changes the file extension Combines path strings.IO. Just prepend [System.Note that all custom drives are added only for your current PowerShell session.Path class includes a number of additionally useful methods that you can use to put together paths or extract information from paths.IO. "ps1") Combine("C:\test".txt") GetFileName("c:\test\file.IO. you could add the path segments together using string operations: PS> $path = [Environment]::GetFolderPath("Desktop") + "\file. "ps1") test. GetFileName() corresponds to Split-Path -leaf Returns the file name without the GetFileNameWithoutExtension() file extension Example ChangeExtension("test.Path] :: to methods listed in Table 15. too.10. which invokes the cmdlet New-Item internally and specifies as -ItemType parameter the Directory value: # "md" is the predefined function and creates new directories: PS> md Test1 Directory: Microsoft. and.PowerShell. Creating New Directories The easiest way to create new directories is to use the Md function.2011 17:14 Length Name -----. move them.---Test2 You can also create several sub-directories in one step as PowerShell automatically creates all the directories that don't exist yet in the specified path: .---Test1 # "New-Item" can do that.txt") IsPathRooted("c:\test\file. corresponds to Split-Path isAbsolute GetFullPath(".PowerShell.txt") GetRandomFileName() GetTempFileName() GetTempPath() HasExtension("c:\test\file.txt") Table 15. if the path includes a file extension True. corresponds to Split-Path qualifier Returns a random file name Returns a temporary file name in the Temp directory Returns the path of the directory for temporary files True. but takes more effort: PS> New-Item Test2 -ItemType Directory Directory: Microsoft. copy them. you can create new files and directories.Core\FileSystem::C:\users\Tobias Weltner Mode ---d---LastWriteTime ------------12.GetFullPath() GetInvalidFileNameChars() GetInvalidPathChars() GetPathRoot() GetRandomFileName() GetTempFileName() GetTempPath() HasExtension() IsPathRooted() Returns the absolute path Lists all characters that are not allowed in a file name Lists all characters that are not allowed in a path Gets the root directory. of course.\test. delete them. fill them with content.txt") GetInvalidFileNameChars() GetInvalidPathChars() GetPathRoot("c:\test\file.4: Methods for constructing paths Working with Files and Directories The cmdlets Get-ChildItem and Get-Item can get you file and directory items that already exist.Core\FileSystem::C:\users\Tobias Weltner Mode ---d---LastWriteTime ------------12. rename them. if the path is absolute. In addition.2011 17:14 Length Name -----.10. txt Get-ChildItem | Out-File info2. file contents look just like they would if you output the information in the console. You can use all of these cmdlets to create text files. this cmdlet is designed to write text to a file. That is because SetContent is not designed to convert objects into text.\info2.Core\FileSystem::C:\users\Tobias Weltner Mode ----a--LastWriteTime ------------10.PowerShell.12.---0 new file. Use -Value if you want to specify text to put into the new file.txt . Instead.hta . By sending that information to Out-File.\report2. Another way to create files is to use old-fashioned redirection using the ">" and ">>" operators.which provides much less information.2011 17:16 Length Name -----. Set-Content works differently: it does not use PowerShell‘s sophisticated ETS (Extended Type System) to convert objects into text. ConvertTo-HTML produces HTML but does not write it to a file.\report1. but it will also make sure that the folder the file is to be created it exists. too. So. The -Force parameter will overwrite any existing file. you can create HTML.txt . redirection and Out-File work very similar: when PowerShell converts pipeline results.hta . For example.txt" -ItemType File Directory: Microsoft. Instead. Set-Content or OutFile.and a bit dangerous.htm If you want to control the "columns" (object properties) that are converted into HTML. Creating New Files You can use New-Item to also create new files. New-Item can create several folders plus a file if you use -Force.or HTA-files and display them.PS> md test\subdirectory\somethingelse Three folders will be created with one line.txt Get-ChildItem | Set-Content info3. creating new files with New-Item becomes even more interesting .\info3.txt As it turns out.txt . simply use Select-Object (Chapter 5): .\info4.txt If you add the -Force parameter. Get-ChildItem > info1. it converts objects into text by using their own private ToString() method .\info1.txt Set-Content info4.txt (Get-Date) . PS> PS> PS> PS> Get-ChildItem | ConvertTo-HTML | Out-File report1. or else you create an empty file: PS> New-Item "new file.hta Get-ChildItem | ConvertTo-HTML | Set-Content report2. length.log | Select-Object -First 10 You can also use -Wait with Get-Content to turn the cmdlet into a monitoring mode: once it read the entire file. and when new content is appended to the file. though: do not mix these methods.log There is a shortcut that uses variable notation if you know the absolute path of the file: PS> ${c:\windows\windowsupdate. it is immediately processed and returned by GetContent. Don't forget to use its -UseCulture parameter to automatically use the delimiter that is right for your culture. use Export-Csv cmdlet instead of ConvertToHTML | Out-File.txt "Third line" PS> Get-Content info. again you can use various methods. In contrast. You would have to hardcode the exact path to the file into your scripts. the old redirection operators have no way of specifying encoding which is why you should avoid using them. LastWriteTime | ConvertTo-HTML | Out-File report. and when you mix encodings. Get-Content reads the contents of a file line by line and passes on every line of text through the pipeline. You can add Select-Object if you want to read only the first 10 lines of a very long file: PS> Get-Content $env:windir\windowsupdate.Get-ChildItem | Select-Object name. The reason is that they all use different default encodings. There is one thing you should keep in mind.txt First Line S e c o n d L i n e Third line All three cmdlets support the -Encoding parameter that you can use to manually pick an encoding.log} However. Reading the Contents of Text Files Use Get-Content to retrieve the contents of a text-based file: PS> Get-Content $env:windir\windowsupdate. stick to one. This is somewhat similar to "tailing" a file in Unix. To add content to an existing file. . You can also pipe results to Out-File and use its -Append parameter to make sure it does not overwrite existing content.htm . the result may look very strange: PS> Set-Content info. this shortcut usually isn‘t very practical because it doesn‘t allow any variables inside curly brackets.txt PS> Add-Content info. it keeps monitoring it.\report.htm If you rather want to export the result as a comma-separated list. or use Add-Content.txt "First line" PS> "Second line" >> info. Either use the appending redirection operator ">>". you can use Select-String to filter information based on keywords and regular expressions. Depending on your culture. and unless you want to completely clutter your desktop. You may use wildcard characters with them. That's why when you forward the filtered information to a file. Moving and Copying Files and Directories Move-Item and Copy-Item perform moving and copying operations. Excel may have picked a different delimiter than the comma. make sure to use the parameter -UseCulture or -Delimiter if the list is not commaseparated. make sure you pick the property Line from the MatchInfo object which holds the text line that matched your keyword: PS> Get-Content $env:windir\windowsupdate.log -Encoding UTF8 | Select-String "successfully installed" | >> Out-File $env:temp\report. To successfully import CSV files. The following line copies all PowerShell scripts from your home directory to the Desktop: PS> Copy-Item $home\*.log | Select-String "successfully installed" Note that Select-String will change the object type to a so-called MatchInfo object.txt >> PS> Invoke-Item $env:temp\report.log -Encoding UTF8 | Select-String "successfully installed" | >> Select-Object -ExpandProperty Line | Out-File $env:temp\report. you'd see the plain text. and UseCulture automatically uses the delimiter that Excel used.txt Accessing Files and Directories Use Import-Csv if you want to process information from comma-separated lists in PowerShell. For example. and then pass the result on to Copy-Item: Before you run this line you should be aware that there may be hundreds of scripts.txt >> PS> Invoke-Item $env:temp\report. When you use Get-Content to read a CSV-file. PS> Get-ChildItem -Filter *. The next line gets only those lines from the windowsupdate.txt To turn the results delivered by Select-String into real text.log file that contain the phrase " successfully installed ": PS> Get-Content $env:windir\windowsupdate. Each column header turns into an object property. A much better way is to use Import-CSV.Finally. It honors the delimiter and returns objects.ps1 ([Environment]::GetFolderPath("Desktop")) Use Get-Childitem to copy recursively. the result lines are cut into pieces: PS> Get-Content $env:windir\windowsupdate. Let it find the PowerShell scripts for you. you could export an Excel spreadsheet as CSV-file and then import the data into PowerShell.ps1 -Recurse | >> Copy-Item -Destination ([Environment]::GetFolderPath("Desktop"))} . you may want to first create a folder on your desktop and then copy the files into that folder. Name.txt testfile.this. PS> Set-Content $env:temp\testfile.txt "Hello.Replace('temporary'. use ForEach-Object to manually feed the incoming information to the appropriate cmdlet parameter.replace('-temporary'. Deleting Files and Directories Use Remove-Item or the Del alias to remove files and folders. '') } When you look at the different code examples. '') } This line would now rename all files and folders. Most file system-related cmdlets are designed to work together. this instruction will suffice: PS> Get-ChildItem | ForEach-Object { Rename-Item $_. even if the term '"-temporary" you're looking for isn't even in the file name. if you wanted to remove the term ―-temporary‖ from a folder and all its sub -directories.csv Bulk Renames Because Rename-Item can be used as a building block in the pipeline. For example. note that ForEach-Object is needed only when a cmdlet cannot handle the input from the upstream cmdlet directly. Renaming files or folders can be dangerous.Name -like "*-temporary" } | >> ForEach-Object { Rename-Item $_. That's why Rename-Item knows how to interpret the output from Get-ChildItem.txt # file opens in Excel now: PS> Rename-Item $env:temp\testfile. use Where-Object to focus only on files that carry the keyword in its name: PS> Get-ChildItem | Where-Object { $_. So. so you could use this code as well: PS> Get-ChildItem | $_.csv PS> Invoke-Item $env:temp\testfile. you'll have to confirm the operation or use the -Force parameter.Name. '') } Rename-Item even accepts a script block.replace('-temporary'. In these situations.an.enumeration" # file opens in notepad: PS> Invoke-Item $env:temp\testfile. as well as all the included files.Name -like '*-temporary' } | Rename-Item { $_. If a file is write-protected.Name. it provides simple solutions to complex tasks.txt -ItemType file # There is no write protection: . so do not rename system files or else Windows may stall. to speed things up and avoid errors.is. # Create an example file: PS> $file = New-Item testfile.Renaming Files and Directories Use Rename-Item if you want to rename files or folders. or if a folder contains data.Name $_.Name $_. It is "Pipeline-aware" and does not need to be wrapped in ForEach-Object. over time .txt PS> del testfile.which is something you should do often when you work with wildcards because they may affect many more files and folders than you initially thought. and that it will delete the correct files. This line.Core\FileSystem::C:\Users\Tobias Weltner\Sources\docs Mode LastWriteTime Length Name .isReadOnly False # Activate write protection: PS> $file. for example.txt: Not enough permission to perform operation.txt -Force Deleting Directory Contents Use wildcard characters if you want to delete a folder content but not the folder itself.PowerShell.* -WhatIf You can as well put this in one line. will empty the Recent folder that keeps track of files you opened lately and .isReadOnly True # Write-protected file may be deleted only by using the –Force parameter: PS> del testfile. Deleting Directories Plus Content PowerShell requests confirmation whenever you attempt to delete a folder that is not empty.can contain hundreds of lnkfiles. Only the deletion of empty folders does not require confirmation: # Create a test directory: md testdirectory Directory: Microsoft. You can always simulate the operation by using -WhatIf to see what happens . PS> $recents = [Environment]::GetFolderPath('Recent') PS> Remove-Item $recents\*. At line:1 char:4 + del <<<< testfile. If you are convinced that your command is correct. Or. Because deleting files and folders is irreversible. too: PS> Get-Childitem ([Environment]::GetFolderPath('Recent')) | Remove-Item WhatIf This however would also delete subfolders contained in your Recent folder because Get-ChildItem lists both files and folders.PS> $file.txt Remove-Item : Cannot remove item C:\Users\Tobias Weltner\testfile. you could use -Confirm instead to manually approve or deny each delete operation.isReadOnly = $true PS> $file. repeat the statement without -WhatIf. be careful. a drive and a file name Moves files and directories Creates new file or new directory Deletes empty directory or file Renames file or directory Resolves relative path or path including wildcard characters Sets property of file or directory Changes to specified directory Extracts a specific part of a path like the parent path. del. cpi Copies file or directory Dir. rmdir. . gci Lists directory contents type. move ni ri. rd rni.1: Overview of the most important file system commands Thanks to PowerShells universal "Provider" concept. for example. you will learn how to read and write Registry keys and Registry values. clear Clears the console window cli Clears file of its contents. but not the file itself copy.10. ren rvpa sp Cd. or file name Returns True if the specified path exists Cmdlet Add-Content Clear-Host Clear-Item Copy-Item Get-Childitem Get-Content Get-Item GetItemProperty Invoke-Item Join-Path Move-Item New-Item Remove-Item Rename-Item Resolve-Path Set-ItemProperty Set-Location Split-Path Test-Path Table 15.2011 13:31 -----. cp.txt "Hello" # Delete directory: PS> del testdirectory Confirm The item at "C:\Users\Tobias Weltner\Sources\docs\testdirectory" has children and the Recurse parameter was not specified. you can navigate the Windows Registry just as you would the file system. drive. all children will be removed with the item. Are you sure you want to continue? [Y] Yes [A] Yes to All [N] No [K] No to All [H] Suspend [?] Help (default is "Y"): To delete folders without confirmation. sl Reads property of a file or directory Invokes file or directory using allocated Windows program Joins two parts of a path into one path.---d---- ------------13. if you continue. chdir. erase.\testdirectory\testfile.---testdirectory # Create a file in the directory: PS> Set-Content . cat. rm. In this chapter. add the parameter -Recurse: PS> Remove-Item testdirectory -Recurse Alias Description ac Adds the contents of a file cls. gc Reads contents of text-based file gi Accesses specific file or directory gp ii mi. ls. mv. PowerSh...PowerSh... . Microsoft... Microsoft...PowerSh. You probably know these cmdlets already: they are used to manage content on drives and all have the keyword "item" in their noun part: PS> Get-Command -Noun Item* CommandType ----------Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Cmdlet Name ---Clear-Item Clear-ItemProperty Copy-Item Copy-ItemProperty Get-Item Get-ItemProperty Invoke-Item Move-Item Move-ItemProperty New-Item New-ItemProperty Remove-Item Remove-ItemProperty Rename-Item Rename-ItemProperty Set-Item Set-ItemProperty ModuleName ---------Microsoft... . Microsoft. . . Microsoft..PowerSh. ... .... ..... .PowerSh... Instead....PowerSh.. . Microsoft..PowerSh.. .. Using Providers o Available Providers o Creating Drives o Searching for Keys o Reading One Registry Value o Reading Multiple Registry Values o Reading Multiple Keys and Values o Creating Registry Keys o Deleting Registry Keys o Creating Values o Securing Registry Keys o Taking Ownership o Setting New Access Permissions o Removing an Access Rule o Controlling Access to Sub-Keys o Revealing Inheritance o Controlling Your Own Inheritance The Registry stores many crucial Windows settings.. Microsoft. That's why it's so cool to read and sometimes change information in the Windows Registry: you can manage a lot of configuration settings and sometimes tweak Windows in ways that are not available via the user interface... .you may well permanently damage your installation.PowerSh. if you mess things up . Microsoft..PowerSh. . Using Providers To access the Windows Registry.. be very careful.PowerSh..change the wrong values or deleting important settings . Microsoft.... and don't change anything that you do not know well.. . Definition ---------.PowerSh..PowerSh...PowerSh.. A provider enables a special set of cmdlets to access data stores... . Microsoft. . Microsoft.. Microsoft...PowerSh.. So.PowerSh. Microsoft......PowerSh.. PowerShell ships with a so-called provider named "Registry".PowerSh.. . However... there are no special cmdlets. Microsoft. Microsoft... Microsoft.. Microsoft... ...... Get-PSProvider Name ---Alias Environment FileSystem Capabilities -----------ShouldProcess ShouldProcess filter. Your list can easily be longer than in the following example. the ActiveDirectory module that ships with Windows Server 2008 R2 (and the RSAT tools for Windows 7) adds a provider for the Active Directory. E. For example. So if you wanted to list the keys of HKEY_LOCAL_MACHINE\Software. ShouldProcess Drives -----{Alias} {Env} {C. the cmdlets probably become a lot more familiar: PS> Get-Alias -Definition *-Item* CommandType ----------Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias Alias ItemProperty Alias ItemProperty Alias Alias Name ---cli clp copy cp cpi cpp del erase gi gp ii mi move mp mv ni rd ren ri rm rmdir rni rnp rp si sp ModuleName ---------Definition ---------Clear-Item Clear-ItemProperty Copy-Item Copy-Item Copy-Item Copy-ItemProperty Remove-Item Remove-Item Get-Item Get-ItemProperty Invoke-Item Move-Item Move-Item Move-ItemProperty Move-Item New-Item Remove-Item Rename-Item Remove-Item Remove-Item Remove-Item Rename-Item RenameRemoveSet-Item Set-ItemProperty Thanks to the "Registry" provider. this is how you'd do it: Dir HKLM:\Software Available Providers Get-PSProvider gets a list of all available providers. all of these cmdlets (and their aliases) can also work with the Registry.Many of these cmdlets have historic aliases. Microsoft SQL Server (starting with 2007) comes with an SQLServer provider. D} . S. Many PowerShell extensions add additional providers. and when you look at those. HKCU} {Variable} {cert} What's interesting here is the ―Drives‖ column. These drives work just like traditional file system drives. More in Chapter Environment 3.function Registry Variable Certificate ShouldProcess ShouldProcess ShouldProcess ShouldProcess {function} {HKLM. which enable you to address a command under another Alias name. Example Dir Alias: $alias:Dir Dir env: $env:windir Dir function: $function:tabexpansion Dir c: $(c:\autoexec.Core\Registry::HKEY_CURRENT_USER SKC VC Name Property --. which lists the drives that are managed by a respective provider. 3 0 Keyboard Layout {} 0 0 Network {} 4 0 Printers {} 38 1 Software {(default)} 2 0 System {} 0 1 SessionInformation {ProgramCount} 1 8 Volatile Environment {LOGONSERVER. Dir variable: Variables are covered in Chapter 3. FileSystem Registry Variable Certificate Provides access to drives. Migrated7. You'll learn more about aliases in Chapter 2. directories and files. Check this out: Cd HKCU: Dir Hive: Microsoft.----------2 0 AppEvents {} 7 1 Console {CurrentPage} 15 0 Control Panel {} 0 2 Environment {TEMP. USERNAME. TMP} 4 0 EUDC {} 1 6 Identities {Identity Ordinal.PowerShell. $variable:pshome Provides access to the certificate store with all its digital certificates... Functions operate much like macros and can Function combine several commands under one name.2: Default providers . These are Dir cert: examined in detail in Chapter 10. the registry provider manages the drives HKLM: (for the registry root HKEY_LOCAL_MACHINE) and HKCU: (for the registry root HKEY_CURRENT_USER). Dir HKLM: Manages all the variables that are defined in the PowerShell console. You can navigate like in the file system and dive deeper into subfolders (which here really are registry keys). Last . Provider Description Manages aliases.bat) Dir HKCU: Provides access to branches of the Windows registry.-. USERDOMAIN. Lists all defined functions.. As you see. Dir cert: -recurse Table 16.. Functions can also be an alternative to aliases and will be described in detail in Chapter 9. Provides access to the environment variables of the system.. Get-PSDrive -PSProvider Registry Name Provider Root CurrentLocation ---------------------------HKCU Registry HKEY_CURRENT_USER HKLM Registry HKEY_LOCAL_MACHINE That's a bit strange because when you open the Registry Editor regedit. you'll see that there are more than just two root hives. they will automatically get removed again.exe. To make this work. Using NewPSDrive. use Remove-PSDrive (which only works if HKU: is not the current drive in your PowerShell console): Remove-PSDrive HKU You can of course create additional drives that point to specific registry keys that you may need to access often. To remove the drive. New-PSDrive InstalledSoftware registry 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall' Dir InstalledSoftware: Note that PowerShell drives are only visible inside the session you defined them. but your new drive HKU: works fine. you now can access all parts of the Windows Registry. add the New-PSDrive statements to your profile script so they get automatically created once you launch PowerShell.Creating Drives PowerShell comes with two drives built-in that point to locations in the Windows Registry: HKLM: and HKCU:. prepend the paths with the provider names like in the example below: Dir Dir Dir Dir HKLM:\Software Registry::HKEY_LOCAL_MACHINE\Software Registry::HKEY_USERS Registry::HKEY_CLASSES_ROOT\.ps1 With this technique. you'd have to add a new drive like this: New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS Dir HKU: You may not have access to all keys due to security settings. you do not need PowerShell drives at all to access the Registry. you can even list all the Registry hives: . Once you close PowerShell. Using Provider Names Directly Actually. If you wanted to access another hive. let's say HKEY_USERS. it can be much easier to work with original Registry paths. To keep additional drives permanently. In many scenarios. though. The registry provider doesn't support filters.Dir Registry:: Searching for Keys Get-ChildItem can list all subkeys of a key.PowerShe ll Note that this example searches both HKCU: and HKLM:. HKLM: -Recurse -Include *PowerShell* -ErrorAction SilentlyContinue | >> Select-Object -ExpandProperty Name >> HKEY_CURRENT_USER\Console\%SystemRoot%_System32_WindowsPowerShell_v1. HKLM: -Recurse -ea 0 | Where-Object { $_. $_. Here is some code that finds all Registry keys that have at least one value with the keyword "PowerShell": PS> Get-ChildItem HKCU:. though. use -Include and -Exclude.GetValue($_) } | Where-Object { $_ -like '*PowerShell*' } } Reading One Registry Value If you need to read a specific Registry value in a Registry key.GetValueNames() | >> ForEach-Object { $key. if you wanted to find all Registry keys that include the word ―PowerShell‖. you will run into keys that are access-protected and would raise ugly "Access Denied" errors.exe HKEY_CURRENT_USER\Software\Microsoft\PowerShell HKEY_CURRENT_USER\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.0_powers hell. Instead. try this: PS> Get-ChildItem HKCU:. All errors are suppressed that way. you cannot use Get-ChildItem to search for Registry values. so you cannot use the parameter -Filter when you search the registry. and it can of course use recursion to search the entire Registry for keys with specific keywords. HKLM: -Recurse -ea 0 | Where-Object { $key = $_. you could search using: PS> Get-ChildItem HKCU:.GetValueNames() | >> Where-Object { $_ -like '*PowerShell*' } } If you want to find all keys that have a value with the keyword in its data. You can search for values indirectly. This example reads the registered owner: PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name RegisteredOwner RegisteredOwner : Tim Telbert . Searching for Values Since Registry values are not interpreted as separate items but rather are added to keys as so-called ItemProperties. For example. The error action is set to SilentlyContinue because in the Registry. use Get-ItemProperty. RegisteredOwner >> ProductName ----------Windows 7 Ultimate EditionID --------Ultimate CSDVersion ---------Service Pack 1 RegisteredOwner --------------Tim Telbert Reading Multiple Keys and Values Yet maybe you want to read values not just from one Registry key but rather a whole bunch of them. use Property instead of –ExpandProperty parameter. EditionID. Simply replace the single Registry value name with a comma-separated list. RegisteredOwner >> ProductName ----------Windows 7 Ultimate EditionID --------Ultimate CSDVersion ---------Service Pack 1 RegisteredOwner --------------Tim Telbert Or. RegisteredOwner | >> Select-Object -Property ProductName. PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' ` >> -Name ProductName.PSPath PSParentPath PSChildName PSDrive PSProvider : Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion : Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT : CurrentVersion : HKLM : Registry Unfortunately. Add another Select-Object to really get back only the content of the value you are after: PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name RegisteredOwner | >> Select-Object -ExpandProperty RegisteredOwner Tim Telbert Reading Multiple Registry Values Maybe you'd like to read more than one Registry value. In HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall. you find a lot of keys. CSDVersion. one for each installed . The code is not much different from before. CSDVersion. EditionID. EditionID. and again use Select-Object to focus only on those. the Registry provider adds a number of additional properties so you don't get back the value alone. Registry keys can hold an unlimited number of values. a little simpler: PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | >> Select-Object -Property ProductName. Since this time you are reading multiple properties. CSDVersion. software product. If you wanted to get a list of all software installed on your machine, you could read all of these keys and display some values from them. That again is just a minor adjustment to the previous code because Get-ItemProperty supports wildcards. Have a look: PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' | >> Select-Object -Property DisplayName, DisplayVersion, UninstallString DisplayName ----------Microsoft IntelliPoint 8.1 {3ED4AD... Microsoft Security Esse... Files\Micro... NVIDIA Drivers C:\Windows\system32\nv... WinImage Files\WinI... Microsoft Antimalware /X{05BFB06... Windows XP Mode /X{1374CC6... Windows Home Server-Con... /I{21E4979... Idera PowerShellPlus Pr... /I{7a71c8a... Intel(R) PROSet/Wireles... (...) DisplayVersion -------------0.8.2.232 8.15.406.0 2.1.1116.0 1.9 "C:\Program 3.0.8402.2 1.3.7600.16422 6.0.3436.0 4.0.2703.2 13.01.1000 MsiExec.exe MsiExec.exe MsiExec.exe MsiExec.exe UninstallString --------------msiexec.exe /I C:\Program Voilá, you get a list of installed software. Some of the lines are empty, though. This occurs when a key does not have the value you are looking for. To remove empty entries, simply add Where-Object like this: PS> Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' | >> Select-Object -Property DisplayName, DisplayVersion, UninstallString | >> Where-Object { $_.DisplayName -ne $null } Creating Registry Keys Since Registry keys are treated like files or folders in the file system, you can create and delete them accordingly. To create new keys, either use historic aliases like md or mkdir, or use the underlying cmdlet directly: PS> New-Item HKCU:\Software\NewKey1 Hive: Registry::HKEY_CURRENT_USER\Software Name ---NewKey1 PS> md HKCU:\Software\NewKey2 Property -------- Hive: Registry::HKEY_CURRENT_USER\Software Name ---NewKey2 Property -------- If a key name includes blank characters, enclose the path in quotation marks. The parent key has to exist. To create a new key with a default value, use New-Item and specify the value and its data type: PS> New-Item HKCU:\Software\NewKey3 -Value 'Default Value Text' -Type String Hive: Registry::HKEY_CURRENT_USER\Software Name ---NewKey3 Property -------(default) : Default Value Text Deleting Registry Keys To delete a key, use the historic aliases from the file system that you would use to delete a folder, or use the underlying cmdlet Remove-Item directly: PS> Remove-Item HKCU:\Software\Test1 Del HKCU:\Software\Test2 Del HKCU:\Software\Test3 This process needs to be manually confirmed if the key you are about to remove contains other keys: Del HKCU:\Software\KeyWithSubKeys Confirm The item at "HKCU:\Software\KeyWithSubKeys" has children and the Recurse parameter was not specified. if you continue, all children will be removed with the item. Are you sure you want to continue? [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): Use the –Recurse parameter to delete such keys without manual confirmation: Del "HKCU:\Software\First key" -Recurse Creating Values Each Registry key can have an unlimited number of values. Earlier in this chapter, you learned how to read these values. Values are called "ItemProperties", so they belong to an "Item", the Registry key. To add new values to a Registry key, either use New-ItemProperty or Set-ItemProperty. New-ItemProperty cannot overwrite an existing value and returns the newly created value in its object form. Set-ItemProperty is more easy going. If the value does not yet exist, it will be created, else changed. Set-ItemProperty does not return any object. Here are some lines of code that first create a Registry key and then add a number of values with different data types: PS> New-Item HKCU:\Software\TestKey4 PS> Set-ItemProperty HKCU:\Software\TestKey4 PS> Set-ItemProperty HKCU:\Software\TestKey4 PS> Set-ItemProperty HKCU:\Software\TestKey4 Type ExpandString PS> Set-ItemProperty HKCU:\Software\TestKey4 Note','Second Note' ` >> -Type MultiString >> PS> Set-ItemProperty HKCU:\Software\TestKey4 4,8,12,200,90 -Type Binary PS> Get-ItemProperty HKCU:\Software\TestKey4 Name ID Path Notes DigitalInfo PSPath PSParentPath PSChildName PSDrive PSProvider : : : : : : : : : : -Name Name -Value 'Smith' -Name ID -Value 12 -Type DWORD -Name Path -Value '%WINDIR%' -Name Notes -Value 'First -Name DigitalInfo -Value Smith 12 C:\Windows {First Note, Second Note} {4, 8, 12, 200...} Registry::HKEY_CURRENT_USER\Software\TestKey4 Registry::HKEY_CURRENT_USER\Software TestKey4 HKCU Registry If you wanted to set the keys' default value, use '(default)' as value name. ItemType String ExpandString Binary DWord MultiString QWord Description A string A string with environment variables that are resolved when invoked Binary values Numeric values Text of several lines 64-bit numeric values DataType REG_SZ REG_EXPAND_SZ REG_BINARY REG_DWORD REG_MULTI_SZ REG_QWORD Table 16.4: Permitted ItemTypes in the registry Use Remove-ItemProperty to remove a value. This line deletes the value Name value that you created in the previous example: Remove-ItemProperty HKCU:\Software\Testkey4 Name Clear-ItemProperty clears the content of a value, but not the value itself. Be sure to delete your test key once you are done playing: Remove-Item HKCU:\Software\Testkey4 -Recurse Securing Registry Keys Registry keys (and its values) can be secured with Access Control Lists (ACLs) in pretty much the same way the NTFS file system manages access permissions to files and folders. Likewise, you can use Get-Acl to show current permissions of a key: md HKCU:\Software\Testkey4 Get-Acl HKCU:\Software\Testkey Path Owner -------Microsoft.PowerShell.Core\Registr... TobiasWeltne-PC\Tobias Weltner TobiasWeltne-PC\Tobias Weltner A... Access ------ To apply new security settings to a key, you need to know the different access rights that can be assigned to a key. Here is how you get a list of these rights: PS> [System.Enum]::GetNames([System.Security.AccessControl.RegistryRights]) QueryValues SetValue CreateSubKey EnumerateSubKeys Notify CreateLink Delete ReadPermissions WriteKey ExecuteKey ReadKey ChangePermissions TakeOwnership FullControl Taking Ownership Always make sure that you are the ―owner‖ of the key before modifying Registry key access permissions. Only owners can recover from lock-out situations, so if you set permissions wrong, you may not be able to undo the changes unless you are the owner of the key. This is how to take ownership of a Registry key (provided your current access permissions allow you to take ownership. You may want to run these examples in a PowerShell console with full privileges): $acl = Get-Acl HKCU:\Software\Testkey $acl.Owner scriptinternals\TobiasWeltner $me = [System.Security.Principal.NTAccount]"$env:userdomain\$env:username" $acl.SetOwner($me) Setting New Access Permissions The next step is to assign new permissions to the key. Let's exclude the group ―Everyone‖ from making changes to this key: $acl = Get-Acl HKCU:\Software\Testkey $person = [System.Security.Principal.NTAccount]"Everyone" $access = [System.Security.AccessControl.RegistryRights]"WriteKey" $inheritance = [System.Security.AccessControl.InheritanceFlags]"None" $propagation = [System.Security.AccessControl.PropagationFlags]"None" $type = [System.Security.AccessControl.AccessControlType]"Deny" $rule = New-Object System.Security.AccessControl.RegistryAccessRule(` $person,$access,$inheritance,$propagation,$type) $acl.AddAccessRule($rule) Set-Acl HKCU:\Software\Testkey $acl The modifications immediately take effect.Try creating new subkeys in the Registry editor or from within PowerShell, and you‘ll get an error message: md HKCU:\Software\Testkey\subkey New-Item : Requested Registry access is not allowed. At line:1 char:34 + param([string[]]$paths); New-Item <<<< -type directory -path $paths Why does the restriction applies to you as an administrator? Aren't you supposed to have full access? No, restrictions always have priority over permissions, and because everyone is a member of the Everyone group, the restriction applies to you as well. This illustrates that you should be extremely careful applying restrictions. A better approach is to assign permissions only. Removing an Access Rule The new rule for Everyone was a complete waste of time after all because it applied to everyone, effectively excluding everyone from the key. So, how do you go about removing a rule? You can use RemoveAccessRule() to remove a particular rule, and RemoveAccessRuleAll() to remove all rules of the same type (permission or restriction) for the user named in the specified rule. ModifyAccessRule() changes an existing rule, and PurgeAccessRules() removes all rules for a certain user. To remove the rule that was just inserted, proceed as follows: $acl = Get-Acl HKCU:\Software\Testkey $person = [System.Security.Principal.NTAccount]"Everyone" $access = [System.Security.AccessControl.RegistryRights]"WriteKey" $inheritance = [System.Security.AccessControl.InheritanceFlags]"None" $propagation = [System.Security.AccessControl.PropagationFlags]"None" $type = [System.Security.AccessControl.AccessControlType]"Deny" $rule = New-Object System.Security.AccessControl.RegistryAccessRule(` $person,$access,$inheritance,$propagation,$type) $acl.RemoveAccessRule($rule) Set-Acl HKCU:\Software\Testkey $acl -Force However, removing your access rule may not be as straightforward because you have effectively locked yourself out. Since you no longer have modification rights to the key, you are no longer allowed to modify the keys' security settings as well. You can overrule this only if you take ownership of the key: Open the Registry editor, navigate to the key, and by right-clicking and then selecting Permissions open the security dialog box and manually remove the entry for Everyone. You‘ve just seen how relatively easy it is to lock yourself out. Be careful with restriction rules. Controlling Access to Sub-Keys In the next example, you use permission rules rather than restriction rules. The task: create a key where only administrators can make changes. Everyone else should just be allowed to read the key. md HKCU:\Software\Testkey2 $acl = Get-Acl HKCU:\Software\Testkey2 # Admins may do everything: $person = [System.Security.Principal.NTAccount]”Administrators” $access = [System.Security.AccessControl.RegistryRights]"FullControl" $inheritance = [System.Security.AccessControl.InheritanceFlags]"None" $propagation = [System.Security.AccessControl.PropagationFlags]"None" $type = [System.Security.AccessControl.AccessControlType]"Allow" $rule = New-Object System.Security.AccessControl.RegistryAccessRule(` $person,$access,$inheritance,$propagation,$type) $acl.ResetAccessRule($rule) # Everyone may only read and create subkeys: $person = [System.Security.Principal.NTAccount]"Everyone" $access = [System.Security.AccessControl.RegistryRights]"ReadKey" $inheritance = [System.Security.AccessControl.InheritanceFlags]"None" $propagation = [System.Security.AccessControl.PropagationFlags]"None" $type = [System.Security.AccessControl.AccessControlType]"Allow" $rule = New-Object System.Security.AccessControl.RegistryAccessRule(` $person,$access,$inheritance,$propagation,$type) $acl.ResetAccessRule($rule) Set-Acl HKCU:\Software\Testkey2 $acl Note that in this case the new rules were not entered by using AddAccessRule() but by ResetAccessRule(). This results in removal of all existing permissions for respective users. Still, the result isn‘t right because regular users could still create subkeys and write values: SetAccessRuleProtection($true. None EM ObjectInherit FullControl Allow BUILT-in\Admi True ContainerInherit. when you look at the permissions again.----------------. $false) Set-Acl HKCU:\Software\Testkey2 $acl Now.----------. It no longer inherits any permissions from parent keys: . the key now contains only the permissions you explicitly set.--------------. It gets these additional permissions by inheritance from parent keys.---------------ReadKey Allow Everyone False None None FullControl Allow BUILT-in\Admi False None None nistrators FullControl Allow TobiasWeltne-PC\T True ContainerInherit. None RICTED ACCESS ObjectInherit The key includes more permissions than what you assigned to it.md hkcu:\software\Testkey2\Subkey Hive: Microsoft. If you want to turn off inheritance.Access | Format-Table -Wrap RegistryRights AccessControlType IdentityReference IsInherited InheritanceFlags PropagationFlags -------------. None nistrators ObjectInherit ReadKey Allow NT AUTHORITY\REST True ContainerInherit.PowerShell. None obias Weltner ObjectInherit FullControl Allow NT AUTHORITY\SYST True ContainerInherit. use SetAccessRuleProtection(): $acl = Get-Acl HKCU:\Software\Testkey2 $acl.----------------.Core\Registry::HKEY_CURRENT_USER\software\Testkey2 SKC --0 VC Name -.---0 Subkey Property -------{} Set-ItemProperty HKCU:\Software\Testkey2 Value1 "Here is text" Revealing Inheritance Look at the current permissions of the key to figure out why your permissions did not work the way you planned: (Get-Acl HKCU:\Software\Testkey2). AccessControl.AccessControl.AccessControl. By no means are they a complete list of what you can do.AccessControl. If you want to pass on your permissions to subdirectories.AccessControlType]"Allow" $rule = New-Object System.AccessControl. Working with Processes o Accessing Process Objects .AccessControlType]"Allow" $rule = New-Object System.ContainerInher it" $propagation = [System.RegistryAccessRule(` $person. Here are all steps required to secure the key: del HKCU:\Software\Testkey2 md HKCU:\Software\Testkey2 $acl = Get-Acl HKCU:\Software\Testkey2 # Admins may do anything: $person = [System.Security. but will your own newly set permissions be propagated to subkeys? Not by default.$access.Security. and event logs so let's take some of the knowledge you gained from the previous chapters and play with it.AccessControl.AccessControl.ResetAccessRule($rule) Set-Acl HKCU:\Software\Testkey2 $acl In your daily work as an administrator.Principal.---------------ReadKey Allow Everyone False None None FullControl Allow BUILT-in\Admistrators False None None ------------ Controlling Your Own Inheritance Inheritance is a sword that cuts both ways. change the setting for propagation.Security.----------------.AccessControl.NTAccount]”Administrators” $access = [System.AccessControl.PropagationFlags]"None" $type = [System.AccessControl. You have just turned off the inheritance of permissions from parent keys.$type) $acl. though.NTAccount]"Everyone" $access = [System.RegistryAccessRule(` $person.$inheritance.Security.InheritanceFlags]"ObjectInherit.$type) $acl. They will provide you with a great starting point.Security.RegistryRights]"ReadKey" $inheritance = [System.$propagation.Security.Security.Security.Security. too.Security. you will probably often deal with applications (processes).$propagation. The examples and topics covered in this chapter are meant to give you an idea of what you can do.RegistryRights]"FullControl" $inheritance = [System.-------------.Security.ContainerInher it" $propagation = [System.PropagationFlags]"None" $type = [System.----------------.ResetAccessRule($rule) # Everyone may only read and create subkeys: $person = [System. services.InheritanceFlags]"ObjectInherit.Security.Principal.$access.$inheritance.RegistryRights AccessControlType IdentityReference IsInherited InheritanceFlags PropagationFlags -------------. .. That's why Get-Process throws a number of exceptions when you try and list the executable files of all running processes... use GetProcess cmdlet. Desktop Window M.. Stopping.. Inc. and Resuming Services Reading and Writing Event Logs o Writing Entries to the Event Log Working with Processes Every application that is running is represented by a so-called "process".. Description. Exceptions occur either when there is no executable for a given process (namely System and Idle). PS> Get-Process This will list all running processes on the local machine... Suspending.. you can only access limited properties of processes you did not launch yourself.. Microsoft Corpor. DataCardMonitor . unless you have local Administrator privileges. o Launching New Processes (Applications) o Using Start Process o Stopping Processes Managing Services o Examining Services o Starting...or its short form -ea 0: PS> Get-Process -FileVersionInfo -ErrorAction SilentlyContinue PS> Get-Process -FileVersionInfo -ea 0 Process objects returned from Get-Process contain a lot more information that you can see when you pipe the result to Select-Object and have it display all object properties: PS> Get-Process | Select-Object * You can then examine the object properties available. their processes may also show up in that list. . Huawei Technolog.. DataCardMonitor Dropbox Dropbox. not just yours. However. or if you do not have permission to see them: PS> Get-Process -FileVersionInfo To hide error messages and focus only on the information you are able to retrieve. and put together your own reports by picking the properties that you need: PS> Get-Process | Select-Object Name. conhost csrss csrss DataCardMonitor Dropbox dwm (. MainWindowTitle Name ---AppleMobileDevic. So if other people are logged onto your box. To view all running processes. use the common parameter ErrorAction SilentlyContinue which is available in every cmdlet .) Description ----------Company ------MainWindowTitle --------------- Console Window Host Microsoft Corpor. Company. .. Technolog. the property MainWindowTitle represents the text in the title bar of an application window.. so you can use both to retrieve processes remotely from other machines. So. Corpor. Name. if a process has no application window.... For example. MainWindowTitle is empty. They occur when a process object has no information for the particular property you selected.MainWindowTitle -ne '' } | >> Select-Object Description. MainWindowTitle....Count PS> Get-Process | Measure-Object -Average -Maximum -Minimum -Property PagedSystemMemorySize Accessing Process Objects . you will get an Access Denied error. you'll notice that there may be blank lines. Company >> Description ----------DataCardMonitor . and unless you are Domain Administrator or otherwise have local Administrator privileges at the target machine.. Note that even with Get-Process. Corpor.. WINWORD Note that you can also retrieve information about processes by using WMI: PS> Get-WmiObject Win32_Process WMI will get you even more details about running processes..AddMinutes(180)} PS> @(Get-Process notepad -ea 0). Both Get-Process and Get-WmiObject support the parameter -ComputerName.When you do that... Get-Process always uses your current identity. For example. Establish an IPC network connection to the target machine. only Get-WmiObject also supports the parameter -Credential so you can authenticate.Remot.StartTime -gt (Get-Date). Use Where-Object to filter out processes that do not meet your requirements. Windows PowerShell Corpor. and use this connection for authentication. Remote Desktop C. MainWindowTitle --------------DataCardMonitor Name ---DataCardMonitor Company ------Huawei Microsoft Microsoft Microsoft storage1 .. Microsoft Office..... this line will get you only processes that do have an application window: PS> Get-Process | Where-Object { $_. you can authenticate. You can use the standard pipeline cmdlets to take care of that. Can you decipher what these lines would do? PS> Get-Process | Where-Object { $_. However. Here is an example: PS> net use \\someRemoteMachine Password /USER:domain\username Here are some more examples of using pipeline cmdlets to refine the results returned by Get-Process. mstsc Windows PowerShell powershell eBook_Chap17_V2. Each Process object contains methods and properties. and press ENTER: PS> notepad PS> regedit PS> ipconfig This works great. it becomes a string (text). When this happens. That's the reason why you can simply enter notepad and press ENTER to launch the Windows Editor. these are the rules to know: Environment variable $env:path: All folders listed in $env:path are special.exe use their own windows for output. Escaping Spaces: If the path name contains spaces. As discussed in detail in Chapter 6. The next statement lowers the priority of all Notepads: PS> Get-Process notepad | ForEach-Object { $_. it shares the console with PowerShell so its output is displayed in the PowerShell console. Applications stored inside these folders can be launched by name only. Once you quote a path.exe. you can specifically raise or lower the priority of a process.exe or regedit. Windowbased applications such as notepad. and in order to run quoted text (and not echo it back). you have to quote them. PowerShell might complain that it would not recognize the application name although you know for sure that it exists. though. the entire path name needs to be quoted. you need to prepend the string with "&" so PowerShell knows that you want to launch something. PowerShell happily echoes the text back to you but won't start the application. PowerShell continues immediately and won't wait for the application to complete. Using Start-Process Whenever you need to launch a new process and want more control. you need to prepend it with an ampersand. The ampersand tells PowerShell to treat the text as if it was a command you entered. it is a bit smarter and knows where a lot of applications are stored. Whenever you quote paths. but eventually you'll run into situations where you cannot seem to launch an application. First of all. Synchronous and asynchronous execution: when you run a console-based application such as ipconfig. That can become tricky because in order to escape spaces in path names. Here. For example.exe or netstat.exe without the need for a path: . so when you press ENTER. So if you wanted to run Internet Explorer from its standard location. this is the line that would do the job: & 'C:\Program Files\Internet Explorer\iexplore.exe' When you run applications from within PowerShell. you need to specify the absolute or relative path name to the application file. and methods can be executed like commands.PriorityClass = "BelowNormal" } Launching New Processes (Applications) Launching applications from PowerShell is pretty straight-forward: simply enter the name of the program you want to run. use Start-Process. This cmdlet has a number of benefits over launching applications directly. It can for example find iexplore. That's why PowerShell pauses until console-based applications finished. or run commands like ping or ipconfig. This allows you to control many fine settings of processes. You do not need to specify the complete or relative path. many properties may be read as well as modified. use -Wait parameter: PS> Start-Process notepad -Wait You'll notice that PowerShell now waits for the Notepad to close again before it accepts new commands. if you wanted PowerShell to wait for a window-based application so a script could execute applications in a strict order. Services provide functionality usually not linked to any individual user. Here is a sample that closes all instances of notepad: PS> Get-Process Notepad -ea 0 | ForEach-Object { $_. use Stop-Process and specify either the process ID. To close a process nicely. Start-Process supports a number of parameters that allow you to control window size. they are always output to the console. Start-Process has just one limitation: it cannot return the results of console-based applications back to you. Use –WhatIf to simulate.PS> Start-Process iexplore. do not use Start-Process. you can close its main window (which is the automation way of closing the application window by a mouse click). it has no time to save unsaved results (which might result in data loss). This would close all instances of the Notepad: PS> Stop-Process -Name Notepad Stopping processes this way shouldn‘t be done on a regular basis: since the application is immediately terminated. So if you want to read information from console-based applications. too. Use –Confirm when you want to have each step confirmed. For example. Use it only if a process won't respond otherwise. Stopping Processes If you must kill a process immediately. Check this out: PS> $result = ipconfig This will store the result of ipconfig in a variable. results are never returned: PS> $result = Start-Process ipconfig -NoNewWindow Instead. or use the parameter Name to specify the process name.exe Second. synchronous or asynchronous execution or even the user context an application is using to run. so ipconfig runs in its own new console window which is visible for a split-second if you look carefully. The same done with Start-Process yields nothing: PS> $result = Start-Process ipconfig That's because Start-Process by default runs every command synchronously. .CloseMainWindow() } Managing Services Services are basically processes. But even if you ask Start-Process to run the command in no new console. and it cannot properly clean up (which might result in orphaned temporary files and inaccurate open DLL counters). They are just executed automatically and in the background and do not necessarily require a user logon. you'd find services that did initialization tasks and finished ok (exit code is 0) or that crashed (exit code other than 0): PS> Get-WmiObject Win32_Service -Filter 'StartMode="Auto" and Started=False' | >> Select-Object DisplayName. you could filter for all services set to start automatically that are not running. For example. it cannot be stopped unless you also specify -Force. temporarily suspend. Stopping. stop. to allow modifications of settings to take effect Resumes a stopped service Modifies settings of a service Starts a service Stops a service Suspends a service Table 17. Suspending. PS> Stop-Service Spooler If a service has dependent services.Cmdlet Get-Service New-Service Restart-Service Resume-Service Set-Service Start-Service Stop-Service Suspend-Service Description Lists services Registers a service Stops a service and then restarts it. PS> Stop-Service Spooler -Force Note that you can use WMI to find out more information about services. and then pipe them to one of the other cmdlets. use the corresponding cmdlets. or restart a service. and also manage services on remote machines: PS> Get-WmiObject Win32_Service -ComputerName Storage1 -Credential Administrator Since WMI includes more information that Get-Service.1: Cmdlets for managing services Examining Services Use Get-Service to list all services and check their basic status. ExitCode >> DisplayName ExitCode . By examining the service ExitCode property. You can also use Get-Service to select the services first. Just note that you may need local administrator privileges to change service properties. PS> Get-Service You can also check an individual service and find out whether it is running or not: PS> Get-Service Spooler Starting. and Resuming Services To start. This cmdlet can wipe out entire event logs. look for a parameter that matches the column name. To find out which log files exist on your machine.30319_X86 0 Microsoft . Logging (writing) event entries no longer necessarily requires administrative privileges. Since we added the event source to the Application . you first need to register an event source (which acts like your "sender" address). To remove an event source. PowerShell can read and write these log files.0. Warning -Message *Time* | >> Select-Object TimeWritten. Once you have registered your event source. you need local administrator privileges. This lists all events from the System event log: PS> Get-EventLog -LogName System Dumping all events is not a good idea. because this is just too much information. Make sure you open PowerShell with full administrator rights and use this line to add a new event source called "PowerShellScript" to the Application log: PS> New-EventLog -LogName Application -Source PowerShellScript Note that an event source must be unique and may not exist already in any other event log. take a look at the column headers. use -LogName instead. and Error.NET Framework NGEN v4. This line gets you the latest 20 errors from the System event log: PS> Get-EventLog -LogName System -EntryType Error -Newest 20 And this line gets you all error and warning entries that have the keyword "Time" in its message: PS> Get-EventLog -LogName System -EntryType Error. you are ready to log things to an event log. but be extremely careful.0. use Remove-EventLog with the same parameters as above. Message Writing Entries to the Event Log To write your own entries to one of the event logs. use Get-EventLog with the parameter -List: PS> Get-EventLog -List To list the content of one of the listed event logs. Warning. In order to filter the information and focus on what you want to know.30319_X64 0 Google Update Service (gupdate) 0 Net Driver HPZ12 0 Pml Driver HPZ12 0 Software Protection 0 Windows Image Acquisition (WIA) 0 Reading and Writing Event Logs Event logs are the central place where applications as well as the operating system log all kinds of information grouped into the categories Information. To register an event source. though.NET Framework NGEN v4.-----------------Microsoft . If you want to filter by the content of a specific column. log, anyone can now use it to log events. You could for example use this line inside of your logon scripts to log status information: PS> Write-EventLog -LogName Application -Source PowerShellScript -EntryType Information ` >> -EventId 123 -Message 'This is my first own event log entry' You can now use Get-EventLog to read back your entries: PS> Get-EventLog -LogName Application -Source PowerShellScript Index Time ----- ---163833 Nov 14 10:47 is... EntryType Source -------------Information PowerShellScript InstanceID Message ---------- ------123 This Or you can open the system dialog to view your new event entry that way: PS> Show-EventLog And of course you can remove your event source if this was just a test and you want to get rid of it again (but you do need administrator privileges again, just like when you created the event source): PS> Remove-EventLog -Source PowerShellScript Windows Management Instrumentation (WMI) is a technique available on all Windows systems starting with Windows 2000. WMI can provide you with a wealth of information about the Windows configuration and setup. It works both locally and remotely, and PowerShell makes accessing WMI a snap. WMI Quick Start Retrieving Information o Exploring WMI Classes o Swallowing The Red Pill o Filtering WMI Results o Direct WMI Object Classes Changing System Configuration o Modifying Properties o Invoking WMI Methods o Static Methods o Using WMI Auto-Documentation WMI Events Using WMI Remotely o Accessing WMI Objects on Another Computer WMI Background Information o Converting the WMI Date Format WMI Quick Start To work with WMI you need to know just a little bit of theory. Let's check out what the terms "class" and "object" stand for. A "class" pretty much is like the "kind of an animal". There are dogs, cats, horses, and each kind is a class. So there is always only one class of a kind. An "object" works like an "animal", so there are zillions of real dogs, cats, and horses. So, there may be one, ten, thousands, or no objects (or "instances") of a class. Let's take the class "mammoth". There are no instances of this class these days. WMI works the same. If you'd like to know something about a computer, you ask WMI about a class, and WMI returns the objects. When you ask for the class "Win32_BIOS", you get back exactly one instance (or object) because your computer has just one BIOS. When you ask for "Win32_Share", you get back a number of instances, one for each share. And when you ask for "Win32_TapeDrive", you get back nothing because most likely, your computer has no built-in tape drive. Tape drives thus work like mammoths in the real world. While there is a class ("kind"), there is no more instance. Retrieving Information How do you ask WMI for objects? It's easy! Just use the cmdlet Get-WmiObject. It accepts a class name and returns objects, just like the cmdlet name and its parameter suggest: PS> Get-WmiObject -Class Win32_BIOS SMBIOSBIOSVersion Manufacturer Name SerialNumber Version : : : : : RKYWSF21 Phoenix Technologies LTD Phoenix TrustedCore(tm) NB Release SP1 1.0 701KIXB007922 PTLTD - 6040000 Exploring WMI Classes As you can see, working with WMI does not require much knowledge. It does require though that you know the name of a WMI class that represents what you are after. Fortunately, Get-WmiObject can also work like a dictionary and look up WMI class names for you. This will get you all WMI class names that have the keyword "print" in them: PS> Get-WmiObject -List Win32_*Print* NameSpace: ROOT\cimv2 Name ---Win32_PrinterConfiguration Captio... Win32_PrinterSetting Win32_PrintJob Da... Win32_Printer Availa... Win32_PrinterDriver ConfigFil... Win32_TCPIPPrinterPort Caption... Methods ------{} {} {Pause, Resume} Properties ---------{BitsPerPel, {Element, Setting} {Caption, Color, {SetPowerState, R... {Attributes, {StartService, St... {Caption, {} {ByteCount, Win32_PrinterShare Depend... Win32_PrinterDriverDll Depend... Win32_PrinterController Antec... {} {} {} {Antecedent, {Antecedent, {AccessState, Swallowing The Red Pill By default, PowerShell limits the information WMI returns to you so you don't get carried away. It's pretty much like in the movie "The Matrix": you need to decide whether you want to swallow the blue pill and live in a simple world, or whether you dare to swallow the red pill and see the real world. By default, you live in the blue-pill-world with only limited information. PS> Get-WmiObject -Class Win32_BIOS SMBIOSBIOSVersion Manufacturer Name SerialNumber Version : : : : : 02LV.MP00.20081121.hkk Phoenix Technologies Ltd. Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk ZAMA93HS600210 SECCSD - 6040000 To see the red-pill-world, pipe the results to Select-Object and ask it to show all available properties: PS> Get-WmiObject -Class Win32_BIOS | Select-Object -Property * Status Name : OK : Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk Caption : Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk SMBIOSPresent : True __GENUS : 2 __CLASS : Win32_BIOS __SUPERCLASS : CIM_BIOSElement __DYNASTY : CIM_ManagedSystemElement __RELPATH : Win32_BIOS.Name="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",SoftwareElementID="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",Softw areElementState=3,TargetOperatingSystem=0,Version="SECC SD - 6040000" __PROPERTY_COUNT : 27 __DERIVATION : {CIM_BIOSElement, CIM_SoftwareElement, CIM_LogicalElement, CIM_ManagedSystemElement} __SERVER : DEMO5 __NAMESPACE : root\cimv2 __PATH : \\DEMO5\root\cimv2:Win32_BIOS.Name="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",SoftwareElementID="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",Softw areElementState=3,TargetOperatingSystem=0,Version="SECC SD - 6040000" BiosCharacteristics : {4, 7, 8, 9...} BIOSVersion : {SECCSD - 6040000, Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk, Ver 1.00PARTTBL} BuildNumber : CodeSet : CurrentLanguage : Description : Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk IdentificationCode : InstallableLanguages : InstallDate : LanguageEdition : ListOfLanguages : Manufacturer : Phoenix Technologies Ltd. OtherTargetOS : PrimaryBIOS : True ReleaseDate : 20081121000000.000000+000 SerialNumber : ZAMA93HS600210 SMBIOSBIOSVersion : 02LV.MP00.20081121.hkk SMBIOSMajorVersion : 2 SMBIOSMinorVersion : 5 SoftwareElementID : Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk SoftwareElementState : 3 TargetOperatingSystem : 0 Version : SECCSD - 6040000 Scope : System.Management.ManagementScope Path : \\DEMO5\root\cimv2:Win32_BIOS.Name="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",SoftwareElementID="Phoenix SecureCore(tm) NB Version 02LV.MP00.20081121.hkk",Softw areElementState=3,TargetOperatingSystem=0,Version="SECC SD - 6040000" Options : System.Management.ObjectGetOptions ClassPath : \\DEMO5\root\cimv2:Win32_BIOS Properties : {BiosCharacteristics, BIOSVersion, BuildNumber, Caption...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Once you see the real world, you can pick the properties you find interesting and then put together a custom selection. Note that PowerShell adds a couple of properties to the object which all start with "__". These properties are available on all WMI objects. __Server is especially useful because it always reports the name of the computer system the WMI object came from. Once you start retrieving WMI information remotely, you should always add __Server to the list of selected properties. PS> Get-WmiObject Win32_BIOS | Select-Object __Server, Manufacturer, SerialNumber, Version __SERVER -------DEMO5 Manufacturer SerialNumber ----------------------Phoenix Technolo... ZAMA93HS600210 Version ------SECCSD - 6040000 Filtering WMI Results Often, there are more instances of a class than you need. For example, when you query for Win32_NetworkAdapter, you get all kinds of network adapters, including virtual adapters and miniports. PowerShell can filter WMI results client-side using Where-Object. So, to get only objects that have a MACAddress, you could use this line: PS> Get-WmiObject Win32_NetworkAdapter | Where-Object { $_.MACAddress -ne $null } | >> Select-Object Name, MACAddress, AdapterType >> Name ---Intel(R) 82567LM-Gigabi... RAS Async Adapter Intel(R) WiFi Link 5100... MACAddress ---------00:13:77:B9:F2:64 20:41:53:59:4E:FF 00:22:FA:D9:E1:50 AdapterType ----------Ethernet 802.3 Wide Area Network (WAN) Ethernet 802.3 Client-side filtering is easy because it really just uses Where-Object to pick out those objects that fulfill a given condition. However, it is slightly inefficient as well. All WMI objects need to travel to your computer first before PowerShell can pick out the ones you want. If you only expect a small number of objects and/or if you are retrieving objects from a local machine, there is no need to create more efficient code. If however you are using WMI remotely via network and/or have to deal with hundreds or even thousands of objects, you should instead use server-side filters. These filters are transmitted to WMI along with your query, and WMI only returns the wanted objects in the first place. Since these filters are managed by WMI and not PowerShell, they use WMI syntax and not PowerShell syntax. Have a look: PS> Get-WmiObject Win32_NetworkAdapter -Filter 'MACAddress != NULL' | >> Select-Object Name, MACAddress, AdapterType >> Name ---Intel(R) 82567LM-Gigabi... RAS Async Adapter Intel(R) WiFi Link 5100... MACAddress ---------00:13:77:B9:F2:64 20:41:53:59:4E:FF 00:22:FA:D9:E1:50 AdapterType ----------Ethernet 802.3 Wide Area Network (WAN) Ethernet 802.3 Simple filters like the one above are almost self-explanatory. WMI uses different operators ("!=" instead of "-ne" for inequality) and keywords ("NULL" instead of $null), but the general logic is the same. Sometimes, however, WMI filters can be tricky. For example, to find all network cards that have an IP address assigned to them, in PowerShell (using client-side filtering) you would use: PS> Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.IPAddress -ne $null } | >> Select-Object Caption, IPAddress, MACAddress >> Caption IPAddress MACAddress -----------------------[00000011] Intel(R) WiF... {192.168.2.109, fe80::a... 00:22:FA:D9:E1:50 If you translated this to a server-side WMI filter, it fails: PS> Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPAddress != NULL' | >> Select-Object Caption, IPAddress, MACAddress >> Get-WmiObject : Invalid query "select * from Win32_NetworkAdapterConfiguration where IPAddress != NULL" The reason for this is the nature of the IPAddress property. When you look at the results from your client-side filtering, you'll notice that the column IPAddress has values in braces and displays more than one IP address. The property IPAddress is an array. WMI filters cannot check array contents. So in this scenario, you would have to either stick to client-side filtering or search for another object property that is not an array and could still separate network cards with IP address from those without. There happens to be a property called IPEnabled that does just that: PS> Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPEnabled = true' | >> Select-Object Caption, IPAddress, MACAddress >> Caption IPAddress MACAddress -----------------------[00000011] Intel(R) WiF... {192.168.2.109, fe80::a... 00:22:FA:D9:E1:50 A special WMI filter operator is "LIKE". It works almost like PowerShell‘s comparison operator -like. Use "%" instead of "*" for wildcards, though. So, to find all services with the keyword "net" in their name, try this: PS> Get-WmiObject Win32_Service -Filter 'Name LIKE "%net%"' | Select-Object Name, DisplayName, State Name ---aspnet_state Net Driver HPZ12 Netlogon Netman NetMsmqActivator NetPipeActivator netprofm NetTcpActivator NetTcpPortSharing DisplayName ----------ASP.NET-Zustandsdienst Net Driver HPZ12 Netlogon Network Connections Net.Msmq Listener Adapter Net.Pipe Listener Adapter Network List Service Net.Tcp Listener Adapter Net.Tcp Port Sharing Se... State ----Stopped Stopped Running Running Stopped Stopped Running Stopped Stopped you don‘t necessarily need to specify the name of the key property as long as you at least specify its value.Name="AudioEndpointBuilder" \\JSMITH-PC\root\cimv2:Win32_Service.Scheduler" \\JSMITH-PC\root\cimv2:Win32_Service.commandline from Win32_Process where name like 'p%'" $searcher.FreeSpace 10181373952 [int]($disk..Name="Automatic LiveUpdate .Get()| Format-Table [a-z]* -Wrap Direct WMI Object Access Every WMI instance has its own unique path.) The path consists basically of the class name as well as one or more key properties.Management.Name="ALG" \\JSMITH-PC\root\cimv2:Win32_Service. which you can use to achieve basically the same thing you just did with the –query parameter: $searcher = [WmiSearcher]"select caption.FreeSpace / 1MB) .Name="BFE" \\JSMITH-PC\root\cimv2:Win32_Service.__PATH } \\JSMITH-PC\root\cimv2:Win32_Service.. For services. First use a "traditional" query to list this property and find out what it looks like: Get-WmiObject Win32_Service | ForEach-Object { $_.Name="AppMgmt" \\JSMITH-PC\root\cimv2:Win32_Service. This path is important if you want to access a particular instance directly.Name="Audiosrv" \\JSMITH-PC\root\cimv2:Win32_Service. Use either the [wmi] type accelerator or the underlying [System.Name="BITS" \\JSMITH-PC\root\cimv2:Win32_Service.WMPNetworkSvc Windows Media Player Ne. The path of a WMI object is located in the __PATH property.Name="Ati External Event Utility" \\JSMITH-PC\root\cimv2:Win32_Service.Name='Fax'" ExitCode : 1077 Name : Fax ProcessId : 0 StartMode : Manual State : Stopped Status : OK In fact. you‘ll find all the properties of a specific WMI instance right away.NET type: [wmi]"Win32_Service.Name="Browser" (.. This way.ManagementObject] . Running PowerShell supports the [WmiSearcher] type accelerator. $disk = [wmi]'Win32_LogicalDisk="C:"' $disk. specify its path and do a type conversion. If you want to work directly with a particular service through WMI. the key property is Name and is the English-language name of the service..Name="AgereModemAudio" \\JSMITH-PC\root\cimv2:Win32_Service.Name="AeLookupSvc" \\JSMITH-PC\root\cimv2:Win32_Service.Name="Appinfo" \\JSMITH-PC\root\cimv2:Win32_Service. . They cannot be used to change the remote system. In addition. If you want to change a remote system using WMI objects. but some are writeable. Most WMI object properties are read-only. a number of WMI objects contain methods that you can call to make changes. you must connect to the remote system using the -ComputerName parameter provided by Get-WmiObject. too.9710 $disk | Format-List [a-z]* Status Availability DeviceID StatusInfo Access BlockSize Caption Compressed ConfigManagerErrorCode ConfigManagerUserConfig CreationClassName Description DriveType ErrorCleared ErrorDescription ErrorMethodology FileSystem FreeSpace InstallDate LastErrorCode MaximumComponentLength MediaType Name NumberOfBlocks PNPDeviceID PowerManagementCapabilities PowerManagementSupported ProviderName Purpose QuotasDisabled QuotasIncomplete QuotasRebuilding Size SupportsDiskQuotas SupportsFileBasedCompression SystemCreationClassName SystemName VolumeDirty VolumeName VolumeSerialNumber : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : C: 0 C: False Win32_LogicalDisk Local hard drive 3 NTFS 10181373952 255 12 C: 100944637952 False True Win32_ComputerSystem JSMITH-PC AC039C05 Changing System Configuration WMIs primary purpose is to read information about the current system configuration but it can also be used to make changes to a system. Note that WMI objects returned by PowerShell Remoting always are read-only. Terminate() .Terminate(). For example. NamespacePath : root\cimv2 ClassName : Win32_LogicalDisk IsClass : False IsInstance : True IsSingleton : False Three conditions must be met before you can modify a property: The property must be writeable.exe" on your local machine: Get-WmiObject Win32_Process -Filter "name='notepad. It lists all codes and clear text translations for all properties and method calls. One of the first links will guide you to the Microsoft MSDN documentation page for that class. the modification will not be written back to the system. Only the property ReturnValue is useful. any other code failure.DeviceID="C:" RelativePath : Win32_LogicalDisk. and you'd be able to kill notepads on one or more remote machines . You must use Put() to save the modification.\root\cimv2:Win32_LogicalDisk. though. That's why it is generally a good idea to add ". though.Modifying Properties Most of the properties that you find in WMI objects are read-only. For example. if you want to change the description of a drive. Of course it is much easier to terminate a process with Stop-Process.exe'" | ForEach-Object { $_.VolumeName = "My Harddrive" $drive. A return value of 0 generally indicates success. you can work on the process directly just as you did in the last section because the process ID is the key property of processes. Invoking WMI Methods WMI objects derived from the Win32_Process class have a Terminate() method you can use to terminate a process. There are few.ReturnValue } Add the parameter -ComputerName to Get-WmiObject. Without Put(). it returns an object with a number of properties. For every instance that Terminate() closes.ReturnValue" to all calls of a WMI method.Put() Path : \\. If you already know the process ID of a process.provided you have Administrator privileges on the remote machine. You require the proper permissions for modifications. that can be modified. The drive description applies to all users of a computer so only administrators may modify them. you could terminate the process with the ID 1234 like this: ([wmi]"Win32_Process='1234'"). because it tells you whether the call succeeded. so why would you use WMI? Because WMI supports remote connections. This line would kill all instances of the Windows Editor "notepad.DeviceID="C:" Server : . Stop-Process can only stop processes on your local machine. To find out what the error codes mean you would have to surf to an Internet search engine and enter the WMI class name (like "Win32_Process"). Most properties are read-only. add new text to the VolumeName property of the drive: $drive = [wmi]"Win32_LogicalDisk='C:'" $drive. ManagementBaseObject GetOwnerSid() SetPriority Method System.Boolean ForceDismount.Automation.Boolean FixErrors.UInt32 Reason) Static Methods There are WMI methods not just in WMI objects that you retrieved with Get-WmiObject.Chkdsk(. use the Win32_NetworkAdapterConfiguration class and its static method RenewDHCPLeaseAll(): .Boolean SkipFolderCycle.Int32 Priority) Terminate Method System.ManagementBaseObject Chkdsk(System.Boolean VigorousIndexCheck.---------AttachDebugger Method System. System.ManagementBaseObject SetPriority(System..Management. However.Boolean RecoverBadSectors.Boolean VigorousIndexCheck.Management.ManagementBaseObject Chkdsk(System.ManagementBaseObject Terminate(System.ManagementBaseObject GetOwner() GetOwnerSid Method System.Boolean SkipFolderCycle. Some WMI classes also support methods..PSMethod Value : System. since this method requires additional arguments.Boolean FixErrors.Boolean ForceDismount. Syst em.Management.Chkdsk MemberType : Method OverloadDefinitions : {System. System.ManagementBaseObject AttachDebugger() GetOwner Method System.If you‘d rather check your hard disk drive C: \ for errors. These methods are called "static". System.ManagementObject#root\cimv2\Win32_Process Name MemberType Definition ------------.Boolean RecoverBadSectors. System.Boolean OkToRunAtBootUp) Name : Chkdsk IsInstance : True Get-Member will tell you which methods a WMI object supports: PS> Get-WmiObject Win32_Process | Get-Member -MemberType Method TypeName: System. System. If you want to renew the IP addresses of all network cards. the question here is what you should specify.Management. Invoke the method without parentheses in order to get initial brief instructions: ([wmi]"Win32_LogicalDisk='C:'"). the proper invocation is: ([wmi]"Win32_LogicalDisk='C:'").Management.Management. System. System. System.Boolean OkToRunAtBootUp)} TypeNameOfValue : System.Management.Management.Management. Syste m. In a similarly way. all the properties of the class are documented.psbase. You can view this description only if you first set a hidden option called UseAmendedQualifiers to $true. For example. It‘s best to limit your search to the Microsoft MSDN pages: Win32_NetworkAdapterConfiguration RenewDHCPLeaseAll site:msdn2. The class returns both local as well as mapped logical disks. refer to Win32 error code documentation. The methods of a WMI class are also documented in detail inside WMI. You can either use the [wmiclass] type accelerator or the underlying [System. The method returns an integer value that can be interpretted as follows: 0 – Successful completion.methods["Win32Shutdown"]). as well as the method. Using WMI Auto-Documentation Nearly every WMI class has a built-in description that explains its purpose.([wmiclass]"Win32_NetworkAdapterConfiguration").Options.Value The Win32Shutdown method provides the full set of shutdown options supported by Win32 operating systems. However.UseAmendedQualifiers = $true (($class.UseAmendedQualifiers = $true ($class.qualifiers["description"]). navigate to an Internet search page like Google and specify as keyword the WMI class name.Type Boolean (($class.psbase.RenewDHCPLeaseAll().psbase.microsoft.UseAmendedQualifiers = $true ($class.properties["VolumeDirty"]).Options. The next example retrieves the documentation for the property VolumeDirty and explains what its purpose is: $class = [wmiclass]'Win32_LogicalDisk' $class. you get the description of the Win32Shutdown() method of the Win32_OperatingSystem class like this: $class = [wmiclass]'Win32_OperatingSystem' $class.ManagementClass] .Options. If you‘d like to learn more about a WMI class or a method.com.Qualifiers["Description"]). Once that‘s done.Value .psbase.Qualifiers["Description"]).properties["VolumeDirty"]).psbase.Value The Win32_LogicalDisk class represents a data source that resolves to an actual local storage device on a Win32 system. the recommended approach is to use this class for obtaining information on local disks and to use the Win32_MappedLogicalDisk for information on mapped logical disk.ReturnVal ue You get the WMI class by using type conversion.Management. Other – for integer values other than those listed above. the WMI class will readily supply information about its function: $class = [wmiclass]'Win32_LogicalDisk' $class. such as a program was shut down or a file deleted. For example.name = 'notepad. __InstanceOperationEvent: This is triggered in all three cases.EventQuery $alarm.__path $live = [wmi]$path # Close Notepad using the live instance $live.terminate() Using WMI Remotely .WaitForNextEvent() “Get target instance of Notepad:” $result. WMI can alert you when one of the following things involving a WMI instance happens: __InstanceCreationEvent: A new instance was added such as a new process was started or a new file created. the more effort involved. type: Select * from __InstanceCreationEvent WITHIN 1 WHERE targetinstance ISA 'Win32_Process' AND targetinstance.name = 'notepad. the computation effort will be scarcely perceptible. The shorter you set the interval. WMI Events WMI returns not only information but can also wait for certain events.exe'" $watch = New-Object Management. __InstanceModificationEvent: The properties of an instance changed. For example.ManagementEventWatcher $alarm “Start Notepad after issuing a wait command:” $result = $watch. The property is applicable to only those instances of logical disk that represent a physical disk in the machine.targetinstance “Access the live instance:” $path = $result.targetinstance.QueryString = "Select * from __InstanceCreationEvent WITHIN 1 WHERE targetinstance ISA 'Win32_Process' AND ` targetinstance. __InstanceDeletionEvent: An instance was deleted. which means that WMI will require commensurately more computing power to perform your task. an action will be started.The VolumeDirty property indicates whether the disk requires chkdsk to be run at next boot up time. Here is an example: $alarm = New-Object Management.exe' WITHIN specifies the time interval of the inspection and ―WITHIN 1‖ means that you want to be informed no later than one second after the event occurs. the FreeSpace property of a drive was modified. You can use these to set up an alarm signal. If the events occur. if you want to be informed as soon as Notepad is started. It is not applicable to mapped logical drives. In the process. As long as the interval is kept at not less than one second. Accessing WMI Objects on Another Computer Use the -ComputerName parameter of Get-WmiObject to access another computer system using WMI. server16 Note that all objects returned by PowerShell Remoting are read-only and do not contain methods anymore. known as Namespaces. Third-party vendors can create additional WMI directories. WMI uses "traditional" remoting techniques like DCOM which are also used by the Microsoft Management Consoles. Here. you can use Get-WmiObject via PowerShell Remoting (if you have set up PowerShell Remoting correctly). you need to have Administrator privileges on the target machine.WMI comes with built-in remoting so you can retrieve WMI objects not just from your local machine but also across the network. your network must support DCOM calls (thus.so it needs to be done before PowerShell Remoting sends back objects to your own system. Then specify the name of the computer after it: Get-WmiObject -ComputerName pc023 Win32_Process You can also specify a comma-separated list of a number of computers and return information from all of them. from its location you can inspect existing namespaces. To be able to use WMI remoting. Get a display first of the namespaces on this level: Get-WmiObject -Namespace root __Namespace | Format-Wide Name subscription DEFAULT MicrosoftDfs CIMV2 Cli nap SECURITY RSOP Infineon WMI . Anything that returns an array of computer names or IP addresses can be valid input. use the –Credential parameter to specify additional log on data as in this example: $credential = Get-Credential Get-WmiObject -ComputerName pc023 -Credential $credential Win32_Process In addition to the built-in remoting capabilities. for example. you send the WMI command off to the remote system: Invoke-Command { Get-WmiObject Win32_BIOS } -ComputerName server12. Up to now. This line. and put in them their own classes. Because the topmost directory in WMI is always named root. would read computer names from a file: Get-WmiObject Win32_Process -ComputerName (Get-Content c:\serverlist.txt) If you want to log on to the target system using another user account. you need to do this inside the script block you send to the remote system . all the classes that you have used have come from the WMI ―directory‖ root\cimv2. which you can use to control software. The parameter -ComputerName accepts an array of computer names. like Microsoft Office or hardware like switches and other equipment. the firewall needs to be set up accordingly). If you want to change WMI properties or call WMI methods. WMI Background Information WMI has a hierarchical structure much like a file system does. Also. For example.StartsWith("Win32_") } Win32_PowerPoint12Tables Win32_Publisher12PageNumber Win32_Publisher12Hyperlink Win32_PowerPointSummary Win32_Word12Fonts Win32_PowerPointActivePresentation Win32_OutlookDefaultFileLocation Win32_Word12Document Win32_ExcelAddIns Win32_PowerPoint12Table Win32_ADOCoreComponents Win32_Publisher12SelectedTable Win32_Word12CharacterStyle Win32_Word12Styles Win32_OutlookSummary Win32_Word12DefaultFileLocation Win32_WordComAddins Win32_PowerPoint12AlternateStartupLoc Win32_OutlookComAddins Win32_ExcelCharts Win32_Word12Settings Win32_FrontPageActiveWeb Win32_OdbcDriver Win32_AccessProject Win32_Word12StartupFileLocation Win32_ExcelActiveWorkbook Win32_FrontPagePageProperty Win32_Publisher12MailMerge Win32_Language Win32_FrontPageAddIns Win32_Word12PageSetup Win32_Word12HeaderAndFooter Win32_ServerExtension Win32_Publisher12ActiveDocumentNoTable Win32_Word12Addin Win32_WordComAddin Win32_PowerPoint12PageNumber Win32_JetCoreComponents Win32_Publisher12Fonts Win32_Word12Table Win32_OutlookAlternateStartupFile Win32_Word12Tables Win32_Access12ComAddins Win32_Excel12AlternateStartupFileLoc Win32_Word12FileConverters Win32_Access12StartupFolder Win32_Word12ParagraphStyle Win32_Access12ComAddin Win32_Excel12StartupFolder Win32_PowerPointPresentation Win32_FrontPageWebProperty Win32_Publisher12Table Win32_Publisher12StartupFolder Win32_WebConnectionErrorText . if you use Microsoft Office. What other directories are shown here depends on the software and hardware that you use. the cimv2 directory is only one of them.directory ServiceModel MSAPPS12 aspnet Policy SecurityCenter Microsoft As you see.Name. Take a look at the classes in it: Get-WmiObject -Namespace root\msapps12 -List | Where-Object { $_. you may find a directory called MSAPPS12. Win32_ExcelSheet Win32_Publisher12Tables Win32_FrontPageTheme Win32_PowerPoint12ComAddins Win32_Word12Template Win32_Access12AlternateStartupFileLoc Win32_Word12ActiveDocument Win32_PublisherSummary Win32_Publisher12DefaultFileLocation Win32_Word12Field Win32_Publisher12Hyperlinks Win32_PowerPoint12ComAddin Win32_PowerPoint12Hyperlink Win32_PowerPoint12DefaultFileLoc Win32_Publisher12Sections Win32_OutlookStartupFolder Win32_Access12JetComponents Win32_Word12ActiveDocumentNotable Win32_Publisher12CharacterStyle Win32_Word12Hyperlinks Win32_Word12FileConverter Win32_PowerPoint12Hyperlinks Win32_FrontPageActivePage Win32_OleDbProvider Win32_Publisher12PageSetup Win32_Word12SelectedTable Win32_PowerPoint12StartupFolder Win32_OdbcCoreComponent Win32_PowerPoint12PageSetup Win32_FrontPageSummary Win32_Word12Hyperlink Win32_Publisher12Font Win32_WebConnectionErrorMessage Win32_AccessDatabase Win32_Publisher12Styles Win32_Publisher12ActiveDocument Win32_Word12AlternateStartupFileLocation Win32_PowerPoint12Fonts Win32_ExcelComAddin Win32_Excel12DefaultFileLoc Win32_Word12Fields Win32_ExcelActiveWorkbookNotable Win32_Publisher12COMAddIn Win32_OutlookComAddin Win32_FrontPageAddIn Win32_WebConnectionError Win32_RDOCoreComponents Win32_Publisher12ParagraphStyle Win32_Publisher12COMAddIns Win32_Transport Win32_Access12DefaultFileLoc Win32_FrontPageThemes Win32_ExcelAddIn Win32_Publisher12AlternateStartupFileLocation Win32_PowerPoint12SelectedTable Win32_ExcelComAddins Win32_Word12MailMerge Win32_Word12Summary Win32_AccessSummary Win32_OfficeWatsonLog Win32_Word12Sections Win32_ExcelWorkbook Win32_PowerPoint12Font Win32_ExcelChart Win32_Word12Font Win32_Word12PageNumber Win32_ExcelSummary . this extension has grown to become a de-facto standard. 2011 8:56:09 AM Now you can also use standard date and time cmdlets such as New-TimeSpan to calculate the current system uptime: New-TimeSpan $realtime (Get-Date) Days : 0 Hours : 6 Minutes : 47 Seconds : 9 Milliseconds : 762 Ticks : 244297628189 TotalDays : 0. For example. This chapter is not talking about either one of these extensions. .7628189 TotalMilliseconds : 24429762. and finally the day. you can use ToDateTime() of the ManagementDateTimeConverter . Following this is the time in hours. This is the so-called DMTF standard. However. October 16. minutes.Converting the WMI Date Format WMI uses special date formats. The AD cmdlets are part of a module called "ActiveDirectory".375199+120 $realtime = [System.282751884478009 TotalHours : 6. and then the time zone.375199+120 LocalDateTime : 20111016153922. you have to go to control panel and enable that feature first. That's why the 3rd party vendor Quest stepped in and published a free PowerShell Snap-In with many useful AD cmdlets. They are included with Server 2008 R2 and also available for download as "RSAT tools (remote server administration toolkit).0.Management. It is introducing you to the build-in low level support for ADSI methods. Beginning with PowerShell Version 2. On a member server or client with installed RSAT tools. Microsoft finally shipped their own AD management cmdlets.498000+120 The date and time are represented a sequence of numbers: first the year.162713648333 TotalSeconds : 24429.ManagementDateTimeConverter]::ToDateTime($boottime) $realtime Tuesday.8189 User administration in the Active Directory was a dark spot in PowerShell Version 1.NET class to decipher this cryptic format: $boottime = (Get-WmiObject win32_OperatingSystem).78604522747222 TotalMinutes : 407. then the month. Over the years. This module is installed by default when you enable the Domain Controller role on a server. and many PowerShell scripts use Quest AD cmdlets. which is hard to read. Microsoft did not ship any cmdlets to manage AD user accounts or other aspects in Active Directory.LastBootUpTime $boottime 20111016085609. and milliseconds. You can freely download this extension from the Quest website. They are the beef that makes these two extensions work and can be called directly. look at Win32_OperatingSystem objects: Get-WmiObject Win32_OperatingSystem | Format-List *time* CurrentTimeZone : 120 LastBootUpTime : 20111016085609. as well. NET framework methods shown in this chapter. it is much easier for you to get one of the mentioned AD extensions and use cmdlets for your tasks. If you (or your scripts) just need to get a user.3: Group Types o Creating New Users Connecting to a Domain If your computer is a member of a domain.Don't get me wrong: if you work a lot with the AD.1: Examples of LDAP queries o Accessing Elements Using GUID Reading and Modifying Properties o Just What Properties Are There? o Practical Approach: Look o Theoretical Approach: Much More Thorough o Reading Properties o Modifying Properties o Deleting Properties Table 19. You can set up a connection like this: $domain = [ADSI]"" $domain distinguishedName ----------------{DC=scriptinternals. the first step in managing users is to connect to a log-on domain. They do not introduce dependencies: your script runs without the need to either install the Quest toolkit or the RSAT tools. it can be easier to use the direct .2: PutEx() operations o The Schema of Domains o Setting Properties Having Several Values Invoking Methods o Changing Passwords o Controlling Group Memberships o In Which Groups Is a User a Member? o Which Users Are Members of a Group? o Adding Users to a Group Creating New Objects o Creating New Organizational Units o Create New Groups Table 19. change some attributes or determine group membership details. Topics Covered: Connecting to a Domain o Logging On Under Other User Names Accessing a Container o Listing Container Contents Accessing Individual Users or Groups o Using Filters and the Pipeline o Directly Accessing Elements o Obtaining Elements from a Container o Searching for Elements Table 19.DC=technet} . For example.exe. you‘ll have to log on to the new domain with an identity known to it." If you want to manage local user accounts and groups. which isn‘t a member of the company domain.If your computer isn‘t a member of a domain.1". That‘s why you could have set up the previous connection this way as well: $domain = [DirectoryServices.distinguishedName DC=scriptinternals. If you must manage local users. But if you have a valid user account along with its password at your disposal.DirectoryEntry]"" $domain distinguishedName ----------------{DC=scriptinternals. but it isn‘t your log-on domain. Your notebook doesn‘t have to be a domain member to access the domain.user" $user | Select-Object * We won‘t go into local user accounts in any more detail in the following examples.DirectoryEntry . also look at net. This prevents you from setting up a connection to the company domain. you can use your notebook and this identity to access the company domain.10. It provides easy to use options to manage local users and groups."domain\user". More likely than not.DC=technet} This is important to know when you want to log on under a different identity.DirectoryEntry .name scriptinternals $domain. But watch out: the text is case-sensitive here. the connection setup will fail and generate an error message: out-lineoutput : Exception retrieving member "ClassId2e4f51ef21dd47e99d3c952918aff9cd": "The specified domain either does not exist or could not be contacted. Logging onto a domain that isn‘t your own with another identity works like this: $domain = new-object DirectoryServices. you can access the local administrator account like this: $user = [ADSI]"WinNT://.10. But why would anyone want to do something like that? Here are a few reasons: External consultant: You may be visiting a company as an external consultant and have brought along your own notebook computer./Administrator. instead of LDAP: use the WinNT: moniker. The [ADSI] type accelerator always logs you on using your current identity. Several domains: Your company has several domains and you want to manage one of them. Only the underlying DirectoryServices.DirectoryEntry("LDAP://10. Logging On Under Other User Names [ADSI] is a shortcut to the DirectoryServices. ` "secret") $domain.NET type gives you the option of logging on with another identity.NET type.DC=technet . Password )) $domain = new-object DirectoryServices./Administrator. surprisingly enough.InteropServices. PowerShell reports errors only when you try to connect with a domain.10.InteropServices. ADSI paths use a normal slash.Message break } else { .1" $useraccount = [ADSI]"WinNT:\\. Bind() always throws an exception and Trap can capture this error.Name property won‘t cause any errors because when the connection fails.Marshal]::SecureStringToBSTR( $cred. That‘s why the two following approaches are wrong: $domain = [ADSI]"ldap://10.10.ErrorCode -ne -2147352570) { Write-Host -Fore Red $err.Two things are important for ADSI paths: first.\Administrator. Then If verifies whether an error occurred.user" # Wrong! # Wrong! If you don‘t want to put log-on data in plain text in your code. use Get-Credential. there isn‘t even any property called Name in the object in $domain. which does the binding.1". $script:err = $null } if ($err. where the error will be stored in a variable.$cred.user" # Wrong! Second.DirectoryEntry("LDAP://10. Since the password has to be given when logging on in plain text.Exception. their names are case-sensitive. If an error occurs in the block.Exception.1" # Wrong! $useraccount = [ADSI]"Winnt://. and Get-Credential returns the password in encrypted form. an intermediate step is required in which it is converted into plain text: $cred = Get-Credential $pwd = [Runtime. $cred = Get-Credential $pwd = [Runtime. how can you find out whether a connection was successful or not? Just invoke the Bind() method.10. This procedure is known as ―binding.UserName. In this event.InteropServices. This is created using script: so that the rest of the script can use the variable.1".DirectoryEntry("LDAP://10. continue } &{ $domain.Marshal]::PtrToStringAuto( [Runtime.$cred. So. A backslash like the one commonly used in the file system would generate error messages: $domain = [ADSI]"LDAP:\\10.10.10.‖ Calling the $domain.Password )) $domain = new-object DirectoryServices. If outputs the text of the error message and stops further instructions from running by using Break.10.10. $pwd) $domain.10.UserName.InteropServices. The code called by Bind() must be in its own scriptblock.Marshal]::SecureStringToBSTR( $cred. $pwd) trap { $script:err = $_ . PowerShell will cut off the block and execute the Trap code.name scriptinternals Log-on errors are initially invisible.Bind($true). A connection error always exists if the exception thrown by Bind() has the -2147352570 error code.Marshal]::PtrToStringAuto( [Runtime. which means it must be enclosed in brackets. $dn" $users Listing Container Contents .distinguishedName $users = [ADSI]"LDAP://$ldap. specify the LDAP path to the container.InteropServices.DirectoryEntry("LDAP://10. Accessing a Container Domains have a hierarchical structure like the file system directory structure.distinguishedName $users = [ADSI]"LDAP://$ldap. specify OU= for organizational units. which is located in the company OU.10.Marshal]::PtrToStringAuto( [Runtime. The LDAP name of the domain is also returned to you by the domain itself in the distinguishedName property. By the way.1$ldap". when you log on as a user to connect to the sales OU.DC=scriptinternals. if you want to access the pre-defined directory Users. All you have to do is specify the container that you want to visit: $ldap = "CN=Users" $domain = [ADSI]"" $dn = $domain.Password )) $users = new-object DirectoryServices. $pwd) $users distinguishedName ----------------{CN=Users.InteropServices.Marshal]::SecureStringToBSTR( $cred..$dn" $users While in the LDAP language pre-defined containers use names including CN=.Write-Host -Fore Green "Connection established.UserName." } Logon failure: unknown user name or bad password. If you want to access a container. Bind() didn’t find an object to which it could bind itself. Containers inside the domain are either pre-defined directories or subsequently created organizational units. That‘s OK because you didn‘t specify any particular object in your LDAP path when the connection was being set up. the error code -2147352570 means that although the connection was established.DC=technet" $cred = Get-Credential $pwd = [Runtime. OU=company" $domain = [ADSI]"" $dn = $domain.10.$cred. For example. you should type: $ldap = "OU=sales.DC=scriptinternals. you could access like this: $ldap = "/CN=Users.DC=technet} The fact that you are logged on as a domain member naturally simplifies the procedure considerably because now you need neither the IP address of the domain controller nor log-on data. So. Among other things.sAMAccountType -eq 805306368 } Another approach makes use of the class that you can always find in the objectClass property. as shown in Chapter 5. not groups. and this object knows the Children property: $ldap = "CN=Users" $domain = [ADSI]"" $dn = $domain.objectClass } admin top person organizationalPerson user .Children | Where-Object { $_.CN=Users.CN=Users.PSBase.) Accessing Individual Users or Groups There are various ways to access individual users or groups. if you want to list only users.distinguishedName $users = [ADSI]"LDAP://$ldap.DC=technet} {CN=All.DC=scriptinternals.distinguishedName $users = [ADSI]"LDAP://$ldap. You can also specifically select individual items from a container or access them directly through their LDAP path.DC=technet} {CN=Consultation2.DC=technet} {CN=Administrator.CN=Users.DC=scriptinternals. For example.DC=technet} (. $users.DC=technet} {CN=Belle.DC=scriptinternals. PowerShell also in the process gets rid of the necessary means to get to the contents of a container.At some point..DC=scriptinternals.DC=scriptinternals.DC=scriptinternals.DC=technet} {CN=Consultation3. Using Filters and the Pipeline Children gets back fully structured objects that. you can process further in the PowerShell pipeline.DC=scriptinternals.CN=Users.Children | Select-Object -first 1 | ForEach-Object { $_. Unfortunately. PowerShell wraps Active Directory objects and adds new properties and methods while removing others.DC=scriptinternals. .CN=Users.sAMAccountName + $_.CN=Users. you can filter the contents of a container.CN=Users. you‘d like to know who or what the container contains to which you have set up a connection.. you could query the sAMAccountType property and use it as a filter criterion: $ldap = "CN=Users" $domain = [ADSI]"" $dn = $domain.$dn" $users. PSBase returns the original (raw) object just like PowerShell received it before conversion.PSBase. The approach here is somewhat less intuitive because now you need the PSBase object.Children distinguishedName ----------------{CN=admin. And you can search for items across directories.DC=technet} {CN=ceimler.PSBase.$dn" $users.DC=technet} {CN=ASPNET.CN=Users. DC=scriptinternals. If you‘re a domain member.) For example.CN=Users. you don‘t have to go to the trouble of using the distinguishedName of the domain: $ldap = "CN=Guest.DC=technet} {Belle} {CN=Belle.PSBase.DC=scriptinternals.Children | Format-Table sAMAccountName.CN=Users.DC=scriptinternals. user} cn : {Guest} description : {Predefined account for guest access to the computer or domain) distinguishedName : {CN=Guest.DC=technet} {Administrator} {CN=Administrator.CN=Users.CN=Users.CN=Users" $domain = [ADSI]"" $dn = $domain.DC=technet} {consultation3} {CN=consultation3..Children | Where-Object { $_.DC=scriptinternals.distinguishedName $guest = [ADSI]"LDAP://$ldap.PSBase.CN=Users. the objectClass property contains an array with all the classes from which the object is derived.CN=Users. You can find the path of an object in the distinguishedName property: $users.DC=scriptinternals.CN=Users.$dn" $guest | Format-List * objectClass : {top.CN=Users. distinguishedName -wrap sAMAccountName distinguishedName -----------------------------{admin} {CN=admin.DC=scriptinternals.DC=technet} instanceType : {4} . organizationalPerson..DC=scriptinternals.DC=technet} {CN=Administrator.DC=technet} {ASPNET} {CN=ASPNET.As it happens.DC=technet} (. specify its distinguishedName. The listing process proceeds from the general to the specific so you can find only those elements that are derived from the user class: $users. if you want to access the Guest account directly..CN=Users.DC=technet} {consultation2} {CN=consultation2.CN=Users.CN=Users.DC=scriptinternals.CN=Users.objectClass -contains "user" } distinguishedName ----------------{CN=admin.DC=technet} {CN=ASPNET.) Directly Accessing Elements If you know the ADSI path to a particular object.DC=scriptinternals.DC=technet} {All} {CN=All.DC=technet} (. you don‘t have to resort to a circuitous approach but can access it directly through the pipeline filter.DC=scriptinternals.DC=technet} {CN=Belle. person.DC=scriptinternals..DC=scriptinternals. Format-List makes all the properties of an ADSI object visible so that you can easily see which information is contained in it and under which names.__ComObject} lastLogon : {System. it can be really difficult to relocate a particular user account or group. In larger domains.__ComObject} primaryGroupID : {514} objectSid : {1 5 0 0 0 0 0 5 21 0 0 0 184 88 34 189 250 183 7 172 165 75 78 29 245 1 0 0} accountExpires : {System.DC=technet} isCriticalSystemObject : {True} nTSecurityDescriptor : {System.DC=technet} uSNChanged : {System.CN=Configuration.2005 12:31:31 PM} whenChanged : {06.__ComObject} logonCount : {0} sAMAccountName : {Guest} sAMAccountType : {805306368} objectCategory : {CN=Person.11.CN=Schema.Children.whenCreated : {12. That‘s why a domain can be accessed and searched like a database.__ComObject} name : {Guest} objectGUID : {240 255 168 180 1 206 85 73 179 24 192 164 100 28 221 74} userAccountControl : {66080} badPwdCount : {0} codePage : {0} countryCode : {0} badPasswordTime : {System.Children.__ComObject} lastLogoff : {System.DC=scriptinternals.CN=Builtin.psbase.Find("CN=Administrator") $useraccount.DC=scriptinternals. Obtaining Elements from a Container You already know what to use to read out all the elements in a container: PSBase. Searching for Elements You‘ve had to know exactly where in the hierarchy of domain a particular element is stored to access it. by using PSBase.Find() you can also retrieve individual elements from a container: $domain = [ADSI]"" $users = $domain.__ComObject} memberOf : {CN=Guests.__ComObject} Using the asterisk as wildcard character.__ComObject} logonHours : {255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 } pwdLastSet : {System.Children. However.27. .Description Predefined account for managing the computer or domain.Find("CN=Users") $useraccount = $users.psbase.2006 09:59:59 AM} uSNCreated : {System. Children.DirectorySearcher($domain) $searcher. It checks accordingly whether the term user turns up in the objectClass property and whether the sAMAccountName property matches the specified user name. note the appropriate lines in the function and specify your log-on data.DirectoryEntry("LDAP://10. # "secret") if ($start -ne "") { $startelement = $domain.Find($start) . function Get-LDAPUser([string]$UserName. which looks a bit strange in this example: $searcher.findall() If you haven‘t logged onto the domain that you want to search.} The crucial part takes place in the search filter.filter = "(&(objectClass=user)(sAMAccountName= $UserName))" $searcher."domain\user".10.1".DC=technet whencreated.DirectoryEntry("LDAP://10.10.10.CN=Users.Once you have logged on to a domain that you want to search. The search function Get-LDAPUser searches the current log-on domain by default.filter = "(&(objectClass=user)(sAMAccountName= $UserName))" $searcher.findall() | Format-Table -wrap The results of the search are all the objects that contain the string ―mini‖ in their names. lastlogon.1/CN=Administrator. get the domain object through the log -on: $domain = new-object DirectoryServices. [string]$Start) { # Use current logon domain: $domain = [ADSI]"" # OR: log on to another domain: # $domain = new-object DirectoryServices. Both criteria are combined by the ―&‖ character.filter = "(&(objectClass=user)(sAMAccountName= $UserName))" The filter merely compares certain properties of elements according to certain requirements. so they both have to be met."secret") $UserName = "*mini*" $searcher = new-object DirectoryServices. If you want to log on to another domain.. Wildcard characters are allowed: $UserName = "*mini*" $searcher = new-object DirectoryServices.10. you need only the following few lines to find all of the user accounts that match the user name in $UserName.10. no matter where they‘re located in the domain: Path Properties ------------LDAP://10. objectsid.DirectorySearcher([ADSI]"") $searcher.psbase."domain\user". nternals.DC=scripti {samaccounttype..1". This would enable you to assemble a convenient search function.10. PageSize specifies in which ―chunk‖ the results of the domain are to be retrieved.OU=company” Get-LDAPUser gets the found user objects right back. How does Get-LDAPUser manage to search only the part of the domain you want it to? The following snippet of code is the reason: if ($start -ne "") { $startelement = $domain.SearchScope = "Subtree" $Searcher. we checked whether the user specified the $start second parameter. Get-LDAPUser *e* “OU=main office. but will .filter = "(&(objectClass=user)(sAMAccountName=$UserName))" $Searcher. If yes.PageSize = 1000 $searcher. or whether the search should be limited to the start directory.PageSize = 1000 SearchScope determines whether all child directories should also be searched recursively beginning from the starting point.Children.CacheResults = $true $Searcher.SearchScope = "Subtree" $Searcher. You can subsequently process them in the PowerShell pipeline—just like the elements that you previously got directly from children.psbase. If you reduce the PageSize. The function also specifies some options that are defined by the user: $Searcher. the starting point is the topmost level of the domain. If $start is missing.DirectorySearcher($startelement) $searcher.} else { $startelement = $domain } $searcher = new-object DirectoryServices. meaning that every location is searched. Just specify the name you‘re looking for or a part of it: # Find all users who have an "e" in their names: Get-LDAPUser *e* # Find only users with "e" in their names that are in the "main office" OU or come under it. your script may respond more freely.Find($start) } else { $startelement = $domain } First.findall() } Get-LDAPUser can be used very flexibly and locates user accounts everywhere inside the domain.CacheResults = $true $Searcher. Find() is used to access the specified container in the domain container (of the topmost level) and this is defined as the starting point for the search. the respective ―chunk‖ will still include only 1.803:=65536)) (&(objectCategory=group)(!groupType: 1.4.840.803:=2147483648)) (&(objectCategory=Computer)(!userAccountControl :1. Here are some useful examples: Search Filter (&(objectCategory=person)(objectClass=User)) (sAMAccountType=805306368) (&.113556.840.840.(objectClass=user)(sn=Weltner) (givenName=Tobias)) (&(objectCategory=person)(objectClass=user) (msNPAllowDialin=TRUE)) (&(objectCategory=person)(objectClass=user) (pwdLastSet=0)) (&(objectCategory=computer)(!description=*)) (&(objectCategory=person)(description=*)) (&(objectCategory=person)(objectClass=user) (whenCreated>=20050318000000.840.1.113556.NativeGUID f0ffa8b401ce5549b318c0a4641cdd4a . where only one condition must be met) Find all disabled user accounts (bitmask logical AND) Find all users whose password never expires Find all users whose password expires (logical NOT using "!") Finding all distribution groups Finding all computer accounts that are not domain controllers Accessing Elements Using GUID Elements in a domain are subject to change.0Z)) (&(objectCategory=person)(objectClass=user) (|(accountExpires=9223372036854775807) (accountExpires=0))) (&(objectClass=user)(userAccountControl: 1.803:=2)) (&(objectCategory=person)(objectClass=user) (userAccountControl:1.4.113556.1. use the practical GetLDAPUser function above: $searchuser = Get-LDAPUser "Guest" $useraccount = $searchuser.803:=8192)) Table 19.1.1.2.2.2.2.4. but harder to read) Find user accounts with a particular name Find user with dial-in permission Find user who has to change password at next logon Find all computer accounts having no description Find all user accounts having no description Find all elements created after March 18. after which it always remains the same.803:=32)) (&(objectClass=user)(!userAccountControl: 1.GetDirectoryEntry() $useraccount. A GUID is assigned just one single time.1: Examples of LDAP queries Description Find only user accounts. You could now freely extend the example function by extending or modifying the search filter. If you request more.113556. not computer accounts Find only user accounts (much quicker.psbase.1.000 data records.4. namely when the object is created.840.113556.4. You can find out the GUID of an element by accessing the account. For example. 2005 Find all users whose account never expires (OR condition.also require more network traffic. The only thing that is really constant is the so-called GUID of an account.2. the GUID. including possible schema extensions. The underlying base object contains the . we will use the Get-LDAPUser function described above to access user accounts. You can find the GUID of an account in PSBase. searching through directory contents. the name. You can get the real ADSI object by using the GetDirectoryEntry() method. In the future. Properties: All the changes you made to ADSI properties won‘t come into effect until you invoke the SetInfo() method. Then you won‘t have to care whethe r the location. You can access the underlying raw object via the PSBase property of the processed object. these are reduced SearchResult objects. you must first use GetDirectoryEntry() to get the real user object. You use the methods and properties of these elements to control them.NativeGUID.CN=Users. You just saw how that happens in the section on GUIDs.DC=scriptinternals. but you can also get at user accounts with one of the other described approaches. This step is only necessary if you want to process search results. everything applies that you read about in Chapter 6. Just What Properties Are There? There are theoretical and a practical approaches to establishing which properties any ADSI object contains. In the following examples. .10. or some other property of the user accounts changes. or launching a search across domains.NET properties and methods you need for general management. The GUID will always remain constant: $acccount = [ADSI]"LDAP://<GUID=f0ffa8b401ce5549b318c0a4641cdd4a>" $acccount distinguishedName ----------------{CN=Guest. In the case of ADSI. you can access precisely this account via its GUID.DC=technet} Specify the GUID when you log on if you want to log on to the domain: $guid = "<GUID=f0ffa8b401ce5549b318c0a4641cdd4a>" $acccount = new-object DirectoryServices.1/$guid". ` "secret") distinguishedName ----------------{CN=Guest.Because the results returned by the search include no ―genuine‖ user objects."domain\user".DC=technet} Reading and Modifying Properties In the last section. you learned how to access individual elements inside a domain: either directly through the ADSI path. as an object PowerShell synthesizes and then as a raw ADSI object. but only reduced SearchResult objects.DirectoryEntry("LDAP://10. The elements you get this way are full-fledged objects. Basically. In reality.DC=scriptinternals. The processed object contains all Active Directory attributes. Phantom objects: Search results of a cross-domain search look like original objects only at first sight.10. there are some additional special features: Twin objects: Every ADSI object actually exists twice: first. You already saw how to access these two objects when you used Children to list the contents of a container.CN=Users. lastlogon.CN=Users.CN=Schema. as you know by now. of the above-mentioned Get-LDAPUser function: $useraccount = Get-LDAPUser Guest $useraccount | Format-List * Path : LDAP://10.DC=technet} instanceType : {4} whenCreated : {12.} The result is meager but.__ComObject} logonHours : {255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 } pwdLastSet : {System.DC=technet} isCriticalSystemObject : {True} nTSecurityDescriptor : {System.__ComObject} name : {Guest} objectGUID : {240 255 168 180 1 206 85 73 179 24 192 164 100 28 221 74} userAccountControl : {66080} badPwdCount : {0} codePage : {0} countryCode : {0} badPasswordTime : {System.CN=Users.DC=technet Properties : {samaccounttype.GetDirectoryEntry() $useraccount | Format-List * objectClass : {top.Practical Approach: Look The practical approach is the simplest one: if you output the object to the console.10. You get the real user object from it by calling GetDirectoryEntry(). search queries only return a reduced SearchResult object. whencreated.2006 09:59:59 AM} uSNCreated : {System.2005 12:31:31 PM} whenChanged : {06. user} cn : {Guest} description : {Predefined account for guest access to the computer or domain) distinguishedName : {CN=Guest.DC=scriptinternals.CN=Configuration.__ComObject} lastLogon : {System. but also right away which values are assigned to the properties.1/CN=Guest.DC=technet} uSNChanged : {System..DC=scriptinternals.DC=scriptinternals.__ComObject} memberOf : {CN=Guests. objectsid.27.__ComObject} lastLogoff : {System. to be precise. PowerShell will convert all the properties it contains into text so that you not only see the properties. the user object is the result of an ADSI search.10. organizationalPerson. In the following example.__ComObject} .. Then you‘ll get more information: $useraccount = $useraccount. person.__ComObject} logonCount : {0} sAMAccountName : {Guest} sAMAccountType : {805306368} objectCategory : {CN=Person.DC=scriptinternals.12.CN=Builtin.__ComObject} primaryGroupID : {514} objectSid : {1 5 0 0 0 0 0 5 21 0 0 0 184 88 34 189 250 183 7 172 165 75 78 29 245 1 0 0} accountExpires : {System. Theoretical Approach: Much More Thorough The practical approach we just saw is quick and returns a lot of information.---------accountExpires Property System. but who called it (Administrator).. further properties are available in the underlying base object: $useraccount.DirectoryServices.PropertyValueCollection countryCode Property System.} countryCode {get.DC=scriptinternals.PropertyValueCollection badPwdCount Property System. cn.PropertyValueCollection cn Property System.} SchemaClassName : user SchemaEntry : System.DirectoryEntry Password : Path : LDAP://10.} description {get. many more properties are available so the tool you need to list them is Get-Member: $useraccount | Get-Member -memberType *Property Name MemberType Definition ------------. description.PropertyValueCollection codePage Property System. among others. distinguishedName.set.PropertyValueCollection badPasswordTime Property System.PropertyValueCollection description Property System. PowerShell shows only those properties in the output that actually do include a value right then (even if it is an empty value).DirectoryServices.1/CN=Guest.set.set.DC=technet Properties : {objectClass.ActiveDirectorySecurity Name : CN=Guest NativeGuid : f0ffa8b401ce5549b318c0a4641cdd4a NativeObject : {} Parent : System.In addition.DirectoryServices.CN=Users.. The UserName property.set.DirectoryServices.set.DirectoryServices.DirectoryServices. for example.DirectoryEntry UsePropertyCache : True Username : scriptinternals\Administrator Options : System.DirectoryServices. does not state whom the user account represents (which in this case is Guest).10.} badPwdCount {get. reports where it is stored inside a domain or what is its unique GUID.PSBase | Format-List * AuthenticationType : Secure Children : {} Guid : b4a8fff0-ce01-4955-b318-c0a4641cdd4a ObjectSecurity : System.set.10. but it is also incomplete. The underlying base object is responsible for the ADSI object itself and.} badPasswordTime {get.} codePage {get.PropertyValueCollection accountExpires {get.DirectoryServices.DirectoryEntryConfiguration Site : Container : The difference between these two objects: the object that was returned first represents the respective user.set.DirectoryServices.DirectoryServices.} cn {get.} .DirectoryServices. In reality. DirectoryServices.set. $useraccount.set.DirectoryServices.} whenCreated {get.PropertyValueCollection name Property System.PropertyValueCollection sAMAccountName Property System.PropertyValueCollection pwdLastSet Property System.PropertyValueCollection uSNCreated Property System.} uSNCreated {get.set. If you change a property.} In this list.set...DirectoryServices.PropertyValueCollection lastLogoff Property System.DirectoryServices.DirectoryServices.PropertyValueCollection memberOf Property System.set.set.PropertyValueCollection distinguishedName {get.set.PropertyValueCollection nTSecurityDescriptor Property System.PropertyValueCollection objectGUID Property System.} objectClass {get. objectCategory {get. Modifiable properties are designated by {get.DirectoryServices..PropertyValueCollection objectClass Property System. you will also learn whether properties are only readable or if they can also be modified. lastLogoff {get.. uSNChanged {get.distinguishedName Property System.} nTSecurityDescriptor {g.PropertyValueCollection uSNChanged Property System.} whenChanged {get.DirectoryServices.DirectoryServices.set.set.PropertyValueCollection lastLogon Property System.} primaryGroupID {get.set.} userAccountControl {get.Description = “guest account” $useraccount.} objectGUID {get.DirectoryServices.PropertyValueCollection whenCreated Property System. the modification won‘t come into effect until you subsequently call SetInfo().} and read-only by {get.} objectSid {get.PropertyValueCollection instanceType Property System..PropertyValueCollection userAccountControl Property System.set.set.PropertyValueCollection isCriticalSystemObject Property System.DirectoryServices.DirectoryServices.} pwdLastSet {get.set.} memberOf {get. instanceType {get.set.DirectoryServices.set.DirectoryServices.} logonCount {get.} logonHours {get.DirectoryServices.PropertyValueCollection objectSid Property System..DirectoryServices.} sAMAccountName {get.set.DirectoryServices.PropertyValueCollection sAMAccountType Property System.DirectoryServices.} isCriticalSystemObject .PropertyValueCollection objectCategory Property System.set.}..set.DirectoryServices.SetInfo() .} lastLogon {get..} sAMAccountType {get.PropertyValueCollection logonCount Property System.DirectoryServices.DirectoryServices.set.} name {get.DirectoryServices.PropertyValueCollection whenChanged Property System.PropertyValueCollection logonHours Property System.PropertyValueCollection primaryGroupID Property System.DirectoryServices.set..DirectoryServices. } Parent Property System.String Password {set.set. formulate: $useraccount.DirectoryServices. $useraccount.String SchemaClassName {get.} Reading Properties The convention is that object properties are read using a dot.InvokeGet("Description") At first glance. if you want to find out what is in the Description property of the $useraccount object.IContainer Container {get. However.Guid Guid {get.} SchemaEntry Property System.String Username {get.Object NativeObject {get.String Path {get.DirectoryServices.ComponentModel.} Container Property System.} UsePropertyCache Property System.DirectoryEntryConfiguration Options {get.ActiveDirectorySecurity ObjectSecurity {get.set.Automation.} Guid Property System.PSBase | Get-Member -MemberType *Property TypeName: System.DirectoryServices.} SchemaClassName Property System.AuthenticationTypes AuthenticationType {get.} Children Property System. both seem to work identically.String Name {get.Description Predefined account for guest access But there are also two other options and they look like this: $useraccount. just like all other objects (see Chapter 6). differences become evident when you query another property: AccountDisabled.} ObjectSecurity Property System.Moreover.} NativeObject Property System.} Properties Property System.Boolean UsePropertyCache {get.psbase. So.DirectoryServices.DirectoryEntry Parent {get.DirectoryEntries Children {get.} Password Property System.DirectoryServices.AccountDisabled .} Path Property System.DirectoryEntry SchemaEntry {get.PSMemberSet Name MemberType Definition ------------.Management. Get-Member can supply information about the underlying PSBase object: $useraccount.set.String NativeGuid {get.PropertyCollection Properties {get.} Username Property System.---------AuthenticationType Property System.ComponentModel.set.DirectoryServices.set.} Site Property System.} Options Property System.Get("Description") $useraccount.} Name Property System.set.} NativeGuid Property System.DirectoryServices.ISite Site {get. and only the third the right result. AccountDisabled is located in another interface of the element as only the underlying PSBase object.Description = "A new description" $useraccount. does everything correctly and returns the contents of this property. As long as you want to work on properties that are displayed when you use Format-List * to output the object to the console.distinguishedName $users = [ADSI]"LDAP://$ldap. However.PSBase.psbase. All attributes (directory properties) become visible in this object as properties.Description." } In fact.GetDirectoryEntry() # Method 1: $useraccount. "Another new description") . The use of a dot categorically suppresses all errors as only Get() reports the problem: nothing was found for this element in the LDAP directory under the name AccountDisabled. there are also a total of three approaches to modifying a property.sAMAccountType -eq 805306368 } | Where-Object { $_.Children | Where-Object { $_. the following line adds a standard description for all users in the user directory if there isn‘t already one: $ldap = "CN=Users" $domain = [ADSI]"" $dn = $domain.” At line:1 Char:14 + $useraccount. PowerShell doesn‘t take these additional properties into consideration.Description = "Standard description".InvokeGet("AccountDisabled") False The first variant returns no information at all.sAMAccountName + " was changed. you won‘t have any difficulty using a dot or Get(). For all other properties. and among these is AccountDisabled.Get( <<<< "AccountDisabled") $useraccount.Use GetEx() iIf you want to have the contents of a property returned as an array.Put("Description". $_. you can modify properties like any other object: use a dot to assign a new value to the property. What happened here? The object in $useraccount is an object processed by PowerShell. That will soon become very important as the three ways behave differently in some respects: $searchuser = Get-LDAPUser Guest $useraccount = $searchuser.toString() -eq "" } | ForEach-Object { $_. That‘s a special feature of ADSI. you‘ll have to use PSBase.Get("AccountDisabled") Exception calling "Get" with 1 Argument(s):"The directory property cannot be found in the cache. For example.$useraccount. with its InvokeGet() method. In fact. the second an error message.SetInfo() # Method 2: $useraccount. $_.SetInfo().$dn" $users. Modifying Properties In a rudimentary case.InvokeGet(). Don‘t forget afterwards to call SetInfo() so that the modification is saved. ADSI objects can contain additional properties. Difficulties arise when you modify properties that have special functions. it will resist: the AccountDisabled property added by PowerShell does not match the AccountDisabled domain property. you have to first return the object to its original state so you basically remove the property that PowerShell added behind your back. .SetInfo() Now the modification works. This shows that GetInfo() is the opposite number of SetInfo(): $useraccount.AccountDisabled The result is ―nothing‖ because this property is—as you already know from the last section—not one of the directory attributes that PowerShell manages in this object.AccountDisabled = $false $useraccount.SetInfo() Exception calling "SetInfo" with 0 Argument(s): "The specified directory service attribute or value already exists.SetInfo( <<<< ) $useraccount. use the third above-mentioned variant to set the property. (Exception from HRESULT: 0x8007200A)" At line:1 Char:18 + $useraccount. all further attempts will fail to store this object in the domain by using SetInfo(). The Guest account is normally disabled: $useraccount.InvokeSet("Description".PSBase. That‘s not good because something very peculiar will occur in PowerShell if you now try to set this property to another value: $useraccount.InvokeSet("AccountDisabled". This problem always occurs when you want to set a property of an ADSI object that hadn‘t previously been specified. You must call GetInfo() or create the object again: Finally. To eliminate the problem. but via its underlying raw version: $useraccount. "A third description") $useraccount.$useraccount. $false) $useraccount.GetInfo() Once PowerShell has added an ―illegal‖ property to the obj ect. You can do that by using GetInfo() to reload the object from the domain. namely not via the normal object processed by PowerShell.AccountDisabled False PowerShell has summarily input to the object a new property called AccountDisabled.SetInfo() As long as you change the normal directory attributes of an object. The other two methods that modify the object processed by PowerShell will only work properly with the properties that the object does display when you output it to the console.SetInfo() # Method 3: $useraccount. If you try to pass this object to the domain. all three methods will work in the same way.psbase. For example among these is the AccountDisabled property. which determines whether an account is disabled or not. The lesson: the only method that can reliably and flawlessly modify properties is InvokeSet() from the underlying PSBase object. 0) $useraccount.Deleting Properties If you want to completely delete a property.12.__ComObject} lastLogon : {System.2005 12:31:31} whenChanged : {17.DC=technet} uSNChanged : {System. organizationalPerson. person.2007 11:59:36} uSNCreated : {System. If you delete a property.PutEx(1. the Description property will be gone completely when you call all the properties of the object: $useraccount | Format-List * objectClass : {top.10.__ComObject} logonHours : {255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 } pwdLastSet : {System. use PutEx() with these parameters: $useraccount.DC=scriptinternals. the third argument is the value that you assign to the property or want to remove from it. The first specifies what PutEx() is supposed to do and corresponds to the values listed in Table 19.__ComObject} memberOf : {CN=Guests.SetInfo() Then. it will be completely removed. .__ComObject} lastLogoff : {System.__ComObject} primaryGroupID : {514} objectSid : {1 5 0 0 0 0 0 5 21 0 0 0 184 88 34 189 250 183 7 172 165 75 78 29 245 1 0 0} accountExpires : {System.2: PutEx() operations To completely remove the Description property. "Description". PutEx() requires three arguments. user} cn : {Guest} distinguishedName : {CN=Guest. Numerical Value 1 2 3 4 Meaning Delete property value (property remains intact) Replace property value completely Add information to a property Delete parts of a property Table 19. you don‘t have to set its contents to 0 or empty text. PutEx() can delete properties and also supports properties that store arrays. Finally.__ComObject} name : {Guest} objectGUID : {240 255 168 180 1 206 85 73 179 24 192 164 100 28 221 74} userAccountControl : {66080} badPwdCount : {0} codePage : {0} countryCode : {0} badPasswordTime : {System.CN=Users.2.CN=Builtin. The second argument is the property name that is supposed to be modified.DC=technet}instanceType : {4} whenCreated : {11.DC=scriptinternals.__ComObject} logonCount : {0} . The result is the schema object for user objects. this doesn‘t mean that the Description property is now gone forever.CN=Configuration") $schema. At line:1 Char:11 + $useraccount. PowerShell always shows only properties that are defined.DC=technet} isCriticalSystemObject : {True} nTSecurityDescriptor : {System. isn‘t it? This means you could add entirely different properties that the object didn‘t have before: $useraccount.systemMayContain | Sort-Object accountExpires aCSPolicyName adminCount badPasswordTime badPwdCount .find("CN=user. That‘s a real deficiency as you have no way to recognize what other properties the ADSI object may possibly support as long as you‘re using PowerShell‘s own resources.psbase. while ―favoritefood‖ was rejected. $schema = $domain.sAMAccountName : {Guest} sAMAccountType : {805306368} objectCategory : {CN=Person.wwwHomePage = "http://www.PSBase. However. which returns the names of all permitted properties in SystemMayContain. The SchemaClass property will tell you which ―operating manual‖ you need for the object: $useraccount.CN=Configuration.SetInfo() Interesting. others (like a home page) are optional.Management. The internal list enables you to get to the properties that you may deposit in an ADSI object.Description = "New description" $useraccount. You can create a new one any time: $useraccount.L <<<< oritefood = "Meatballs" $useraccount.CN=Schema.PSMethod".SchemaClassName user Take a look under this name in the schema of the domain.CN=Schema. Some information is mandatory and has to be specified for every object of the type. The Schema of Domains The directory service comes equipped with a list of permitted data called a schema to prevent meaningless garbage from getting stored in the directory service.powershell.Automation.SetInfo() It turns out that the user account accepts the wwwHomePage property (and so sets the Web page of the user on user properties). Only properties allowed by the schema can be set.Children.favoritefood = "Meatballs" Cannot set the Value property for PSMemberInfo object of type "System.DC=scriptinternals.com" $useraccount.__ComObject} ImportantEven Get-Member won‘t return to you any more indications of the Description property.. businessCategory codepage controlAccessRights dBCSPwd defaultClassStore desktopProfile dynamicLDAPServer groupMembershipSAM groupPriority groupsToIgnore homeDirectory homeDrive homePhone initials lastLogoff lastLogon lastLogonTimestamp lmPwdHistory localeID lockoutTime logonCount logonHours logonWorkstation mail manager maxStorage mobile msCOM-UserPartitionSetLink msDRM-IdentityCertificate msDS-Cached-Membership msDS-Cached-Membership-Time-Stamp mS-DS-CreatorSID msDS-Site-Affinity msDS-User-Account-Control-Computed msIIS-FTPDir msIIS-FTPRoot mSMQDigests mSMQDigestsMig mSMQSignCertificates mSMQSignCertificatesMig msNPAllowDialin msNPCallingStationID msNPSavedCallingStationID msRADIUSCallbackNumber msRADIUSFramedIPAddress msRADIUSFramedRoute msRADIUSServiceType msRASSavedCallbackNumber msRASSavedFramedIPAddress msRASSavedFramedRoute networkAddress ntPwdHistory o operatorCount otherLoginWorkstations pager preferredOU . psbase | Get-Member -memberType *Method TypeName: System. "456". methods do not require you to call SetInfo() when you invoke a method that modifies an object. In contrast to properties. The property can store just one telephone number or several. "789")) $useraccount. "otherHomePhone".Automation. Among these is otherHomePhone. "789")) $useraccount.Management. proceed as follows: $useraccount. @("456".PSMemberSet . the list of a user‘s supplementary telephone contacts. If you want to add a new telephone number to an existing list.PutEx(2. which you get by using PSBase: $guest.PutEx(4.PutEx(3.primaryGroupID profilePath pwdLastSet scriptPath servicePrincipalName terminalServer unicodePwd userAccountControl userCertificate userParameters userPrincipalName userSharedFolder userSharedFolderOther userWorkstations Setting Properties Having Several Values PutEx() is not only the right tool for deleting properties but also for properties that have more than one value. "otherHomePhone". which is how you can reset the property telephone numbers: $useraccount. @("555")) $useraccount.SetInfo() But note that this would delete any other previously entered telephone numbers. . "otherHomePhone". To find out which methods an object contains. but also methods. @("123". The true functionality is in the base object.SetInfo() Invoking Methods All the objects that you‘ve been working with up to now contain not only properties.SetInfo() A very similar method allows you to delete selected telephone numbers on the list: $useraccount. the result is something of a disappointment because the ADSI object PowerShell delivers contains no methods. use Get-Member to make them visible (see Chapter 6): $guest | Get-Member -memberType *Method Surprisingly. Object Invoke(String methodName.ComponentModel.ISite get_Site() get_UsePropertyCache Method System..DirectoryServices.DirectoryEntryConfiguration get_Options() get_Parent Method System.DirectoryServices.ActiveDirectorySecurity get_ObjectSecurity() get_Options Method System..Void Dispose() Equals Method System..Void add_Disposed(EventHandler value) Close Method System.DirectoryServices.Void DeleteTree() Dispose Method System.String get_Username() InitializeLifetimeService Method System.DirectoryEntry get_Parent() get_Path Method System.IContainer get_Container() get_Guid Method System.String get_Name() get_NativeGuid Method System.Void CommitChanges() CopyTo Method System.String get_NativeGuid() get_ObjectSecurity Method System.PropertyCollection get_Properties() get_SchemaClassName Method System.Void Rename(String newName) .Void InvokeSet(String propertyName.DirectoryServices..AuthenticationTypes get_AuthenticationType() get_Children Method System.DirectoryEntry CopyTo(DirectoryEntry newPare.Void RefreshCache(String[] propert.Void MoveTo(DirectoryEntry newParent).Runtime.Type GetType() get_AuthenticationType Method System.String get_Path() get_Properties Method System.DirectoryServices. CreateObjRef Method System.Object GetLifetimeService() GetType Method System.Void MoveTo(Dire.. RefreshCache Method System.String get_SchemaClassName() get_SchemaEntry Method System.ObjRef CreateObjRef(Type requestedType) DeleteTree Method System.DirectoryServices.Void remove_Disposed(EventHandler value) Rename Method System.DirectoryServices.DirectoryServices. System.DirectoryEntries get_Children() get_Container Method System.Void Close() CommitChanges Method System. System. remove_Disposed Method System.Guid get_Guid() get_Name Method System. Params Object[] args) InvokeGet Method System.Name MemberType Definition ------------.---------add_Disposed Method System.Object InitializeLifetimeService() Invoke Method System.Void RefreshCache().DirectoryEntry get_SchemaEntry() get_Site Method System.Object InvokeGet(String propertyName) InvokeSet Method System.Boolean Equals(Object obj) GetHashCode Method System. Params Object[] args) MoveTo Method System..Remoting.Int32 GetHashCode() GetLifetimeService Method System.Boolean get_UsePropertyCache() get_Username Method System.ComponentModel. [string]$Start) { # Use current logon domain: $domain = [ADSI]"" . the deficiencies of Get-Member become evident when it is used with ADSI objects because Get-Member suppresses both methods instead of displaying them. though it‘s necessary when users forget their passwords. methods ensure the immediate generation of a completely confidential hash value out of the user account and that it is deposited in a secure location. too.Void set_Path(String value) set_Site Method System. you‘ll be rewa rded with an error message like this one: Exception calling "SetPassword" with 1 Argument(s): "The password does not meet the password policy requirements.Void set_ObjectSecurity(ActiveDirectorySecurity value) set_Password Method System. Instead. Alternatively.String ToString() Changing Passwords The password of a user account is an example of information that isn‘t stored in a property. the first thing you need is the groups in which a user becomes a member. You just have to ―know‖ that they exist. password complexity and password history requirements.ChangePassword("Old password".SetPassword( <<<< "secret") Controlling Group Memberships Methods also set group memberships. including the crucial certificate for the Encrypting File System (EFS). That can be risky because in the process you lose access to all your certificates outside a domain. When you change a password. "New password") Here.Void set_AuthenticationType(AuthenticationTypes value) set_ObjectSecurity Method System. That‘s why you can‘t just read out user accounts. Otherwise.Void set_UsePropertyCache(Boolean value) set_Username Method System. be sure that it meets the demands of the domain.Void set_Username(String value) ToString Method System.set_AuthenticationType Method System. ChangePassword doesn‘t need any higher level of permission because confirmation requires giving the old password. you can use a universal function that helpfully picks out groups for you: function Get-LDAPGroup([string]$UserName.SetPassword("New password") $useraccount. SetPassword() requires administrator privileges and simply resets the password.Void set_Site(ISite value) set_UsePropertyCache Method System. Check the minimum password length. You can use the SetPassword() and ChangePassword() methods to change passwords: $useraccount.Void set_Password(String value) set_Path Method System. Of course. That basically works just like user accounts as you could specify the ADSI path to a group to access the group. (Exception from HRESULT: 0x800708C5)" At line:1 Char:22 + $realuser. use Add(): $administrators = (Get-LDAPGroup “Domain Admins”).CN=Users.DC=scriptinternals.CN=Users.DC=scriptinternals.findall() } In Which Groups Is a User a Member? There are two sides to group memberships.DC=scriptinternals.DC=scriptinternals. you need the group object as well as (at least) the ADSI path of the user.DC=scriptinternals.DirectorySearcher($startelement) $searcher. Once you get the user account object. So.DC=technet CN=Markus2.CacheResults = $true $Searcher. but also MemberOf with the groups in which this group is itself a member.PageSize = 1000 $searcher. Adding Users to a Group To add a new user to a group.CN=Builtin.filter = "(&(objectClass=group)(sAMAccountName=$UserName))" $Searcher. the memberOf property will return the groups in which the user is a member: $guest = (Get-LDAPUser Guest). To do this.CN=Users. # "secret") if ($start -ne "") { $startelement = $domain.member CN=Tobias Weltner.psbase.GetDirectoryEntry() $admin.GetDirectoryEntry() $guest."domain\user". who is supposed to become a member.DC=technet Groups on their part can also be members in other groups.Children. every group object has not only the Member property with its members.GetDirectoryEntry() .DC=technet CN=Belle.10.memberOf CN=Guests.DC=technet Which Users Are Members of a Group? The other way of looking at it starts out from the group: members are in the Member property in group objects: $admin = (Get-LDAPGroup "Domain Admins").DC=technet CN=Administrator.Find($start) } else { $startelement = $domain } $searcher = new-object DirectoryServices.SearchScope = "Subtree" $Searcher.1".DirectoryEntry("LDAP://10.10.# OR: log on to another domain: # $domain = new-object DirectoryServices.CN=Users. It would have sufficed to specify the user‘s correct ADSI path to the Add() method. Then.distinguishedName $administrators. Creating New Objects The containers at the beginning of this chapter also know how to handle properties and methods.Create("organizationalUnit".Member += $user. In addition.psbase.Create("organizationalUnit".SetInfo() Instead of Add() use the Remove() method to remove users from the group again.Member = $administrators. get a domain object: $domain = [ADSI]"" Next. Creating New Organizational Units Let‘s begin experimenting with new organizational units that are supposed to represent the struct ure of a company. define with the groupType property the type of group that you want to create. So. "OU=Marketing") $marketing.Create("organizationalUnit".$user = (Get-LDAPUser Cofi1). But it‘s easier to get the user and pass the path property of the PSBase object. Aside from Add(). You should decide again in which container the group is to be created and specify the name of the group..SetInfo() $service = $company. groups.SetInfo() $administrators.SetInfo() Create New Groups Groups can be created as easily as organizational units. there are other ways to add users to groups: $administrators.SetInfo() In the example.Create("organizationalUnit".distinguishedName $administrators.Path) $administrators.SetInfo() $sales = $company.SetInfo() $marketing = $company. create a new organizational unit called ―company‖ and under it some additional organizational units: $company = $domain.Member + $user.Add($user. because in contrast to organizational units there are several different types of groups: . the user Cofi1 is added to the group of Domain Admins. all you have to do is to decide where these elements should be stored inside a domain. Since the first organizational unit should be created on the topmost domain level. use the Create() method of the respective container. "OU=Sales") $sales. if you want to create new organizational units.GetDirectoryEntry() $administrators. "OU=Idera") $company. "OU=Service") $service. and users. SetInfo() $user.SetPassword("TopSecret99") $user. The following lines create a new user account in the previously created organization unit ―Sales‖: $user = $sales. and first create the new user object in a container of your choice. but have no security function.InvokeSet("groupType". a global security group and a global distribution group are created: $group_marketing = $marketing. Then.NET Libraries and Compiling Code More Sharing ServicesShare | Share on twitterShare on facebookShare on myspaceShare on bloggerShare on liveShare on googleShare on email . 2) $group_newsletter. you can fill out the required properties and set the password using SetPassword(). Chapter 20. proceed analogously.Create("group". enable the account. "CN=Marketinglights") $group_marketing.Group Global Local Universal As security group Code 2 4 8 Add -2147483648 Table 19. In the following example.InvokeSet('AccountDisabled'. Using the AccountDisabled property. "CN=MyNewUser") $user.InvokeSet("groupType".psbase.SetInfo() # $group_newsletter = $company.SetInfo() Instead of Create() use the Delete() method to delete objects.psbase. Distribution groups organize only members.3: Group Types Security groups have their own security ID so you can assign permissions to them.Description = "My New User" $user.SetInfo() Creating New Users To create a new user. -2147483648 + 2) $group_marketing.psbase. "CN=Newsletter") $group_newsletter. $false) $user. Loading .Create("User"..Create("group". Managing Scope Chapter 13. Interactive PowerShell Chapter 3. Text and Regular Expressions Chapter 14. Services. Arrays and Hashtables Chapter 5. XML Chapter 15. you already know from Chapter 6 how you can use . WMI: Windows Management Instrumentation Chapter 19.Table of Contents Chapter 1. For example. Working with the File System Chapter 16. The PowerShell Pipeline Chapter 6.NET Framework are available right in PowerShell. and Event Logs Chapter 18. the following two lines suffices to set up a dialog window: Add-Type -assembly Microsoft. Loops Chapter 9.NET Libraries Creating New . Working with Objects Chapter 7. You should be able to even create your own cmdlets at the end of this chapter. Topics Covered: Loading . You‘ll learn about the options PowerShell has for creating command extensions on the basis of the .NET Libraries and Compiling Code Since PowerShell is layered on the . Processes. Conditions Chapter 8. Managing Windows Registry Chapter 17. Loading .VisualBasic . In this chapter. The PowerShell Console Chapter 2.NET code in PowerShell to make up for missing functions.NET Libraries Many functionalities of the . Functions Chapter 10. Variables Chapter 4. we‘ll take up this idea once again. User Management Chapter 20.NET Framework.NET Framework.NET Libraries o In-Memory Compiling o DLL Compilation Loading . Error Handling Chapter 12. Scripts Chapter 11. NET. The only way to get it done is to access the low-level API functions outside the . mem) CloseClipboard() End If End If End Sub End Class End Namespace '@ . you learned in detail about how this works and what an ―assembly‖ is. ByVal dwBytes As Integer) As Integer Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Integer) As Integer Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Integer) As Integer Private Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Integer. ByVal hMem As Integer) As Integer Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Integer.NET Framework.VisualBasic.NET Libraries As soon as you need more than just a few lines of code or access to API functions to implement the kinds of extensions you want. it makes sense to write the extension directly in . "Question") In Chapter 6. text. "YesNoCancel.[Microsoft. For example.NET Framework doesn‘t have any right commands. Creating New . The VB.Interaction]::MsgBox("Do you agree?".VisualBasic Imports System Namespace ClipboardAddon Public Class Utility Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Integer) As Integer Private Declare Function EmptyClipboard Lib "user32" () As Integer Private Declare Function CloseClipboard Lib "user32" () As Integer Private Declare Function SetClipboardData Lib "user32"(ByVal wFormat As Integer. ByVal lpString2 As String) As Integer Public Sub CopyToClipboard(ByVal text As String) Dim result As Boolean = False Dim mem As Integer = GlobalAlloc(&H42. you have to r ely on your own resources if you want to move text to the clipboard.NET program code.Length + 1) Dim lockedmem As Integer = GlobalLock(mem) lstrcpy(lockedmem. text) If GlobalUnlock(mem) = 0 Then If OpenClipboard(0) Then EmptyClipboard() result = SetClipboardData(1. The following example shows how a method called CopyToClipboard() might look in VB.NET code is assigned to the $code variable as plain text: $code = @' Imports Microsoft.Question". but for some functionality even the . PowerShell used Add-Type to load a system library and was then able to use the classes from it to call a static method like MsgBox(). That‘s extremely useful when there is already a system library that offers the method you‘re looking for. methods can also be static.You have to first compile the code before PowerShell can execute it. What if you wanted to protect your intellectual property somewhat and compile a DLL that your solution would then load? Here is how you create your own DLL (make sure the folder c:\powershell exists. Static methods are called directly through the class in which they are defined. For example. then you can immediately call the method like this: [ClipboardAddon. Compilation is a translation of your source code into machine-readable intermediate language (IL). you need neither New-Object nor any instances. Replace this line: Public Sub CopyToClipboard(ByVal text As String) Type this line instead: Public Shared Sub CopyToClipboard(ByVal text As String) Once you have compiled your source code.Utility $object. feed the source code to Add-Type and specify the programming language the source code used: $type = Add-Type -TypeDefinition $code -Language VisualBasic Now. you can derive an object from your new type and call the method CopyToClipboad().CopyToClipboard(“Hi Everyone!”) You might be wondering why in your custom type. your source code was compiled in-memory on the fly. you can even compile and generate files. MsgBox() in the first example is a static method. In-Memory Compiling To compile the source code and make it a type that you can use. you could call that method directly from the type. or else create it or change the output path in the command below): PS> $code = @' . With MsgBox() in the previous example. If you would rather use CopyToClipboard() as a static method. which requires you to first create an instance of the class. Then the instance can call the method. Alternatively. To call static methods. and that‘s exactly what New-Object does. In the previous example. CopyToClipboard() is created in your source code as a dynamic method.Utility]::CopyToClipboard(“Hi Everyone!”) DLL Compilation With Add-Type. all you need to do is to make a slight change to your source code. you needed to use New-Object first to get an object. There are two options here. Done! $object = New-Object ClipboardAddon. Length + 1) Dim lockedmem As Integer = GlobalLock(mem) lstrcpy(lockedmem.Imports Microsoft. text. ByVal dwBytes As Integer) As Integer Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Integer) As Integer Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Integer) As Integer Private Declare Function lstrcpy Lib "kernel32" (ByVal lpString1 As Integer. you better use a specific development environment like Visual Studio. .dll After you run these commands. try this code in a new PowerShell console. Your experiments with the in-memory compilation may have interfered. If not.dll PS> [Clipboardaddon. text) If GlobalUnlock(mem) = 0 Then If OpenClipboard(0) Then EmptyClipboard() result = SetClipboardData(1.utility]::CopyToClipboard("Hello World!") You can even compile and create console applications and windows programs that way . To load and use your DLL from any PowerShell session.dll with the compiled content of your code.although that is an edge case. mem) CloseClipboard() End If End If End Sub End Class End Namespace '@ PS> Add-Type -TypeDefinition $code -Language VisualBasic -OutputType Library ` >> -OutputAssembly c:\powershell\extension. ByVal lpString2 As String) As Integer Public Shared Sub CopyToClipboard(ByVal text As String) Dim result As Boolean = False Dim mem As Integer = GlobalAlloc(&H42.VisualBasic Imports System Namespace ClipboardAddon Public Class Utility Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Integer) As Integer Private Declare Function EmptyClipboard Lib "user32" () As Integer Private Declare Function CloseClipboard Lib "user32" () As Integer Private Declare Function SetClipboardData Lib "user32"(ByVal wFormat As Integer. you should find a file called c:\powershell\extension. go ahead and use this code: PS> Add-Type -Path C:\powershell\extension. To create applications. ByVal hMem As Integer) As Integer Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Integer.