Skip to main content

Windows

Program: Start a Process As Another User in PowerShell

Discussion

If your script requires user credentials, you will want to store those credentials in a PowerShell PsCredential object. This lets you securely store those credentials, or pass them to other commands that accept PowerShell credentials. When you write a script that accepts credentials, consider letting the user to supply either a username or a preexisting credential. Example 164 demonstrates a useful approach that allows that. As the framework for this demonstration, the script lets you start a process as another user.

Example 164. StartProcessAsUser.ps1

############################################################################## ## ## StartProcessAsUser.ps1 ## ## Launch a process under alternate credentials, providing functionality ## similar to runas.exe. ## ## ie: ## ## PS >$file = JoinPath ([Environment]::GetFolderPath("System")) certmgr.msc ## PS >StartProcessAsUser Administrator mmc $file ## ## ## ##############################################################################

param( $credential = (GetCredential), [string] $process = $(throw "Please specify a process to start."), [string] $arguments = "" )

## Create a real credential if they supplied a username if($credential is "String") {

$credential = GetCredential $credential }

## Exit if they canceled out of the credential dialog if(not ($credential is "System.Management.Automation.PsCredential")) {

return }

## Prepare the startup information (including username and password) $startInfo = NewObject Diagnostics.ProcessStartInfo $startInfo.Filename = $process

Example 164. StartProcessAsUser.ps1 (continued)

$startInfo.Arguments = $arguments

## If we're launching as ourselves, set the "runas" verb if(($credential.Username eq "$ENV:Username") or ($credential.Username eq "\$ENV:Username")) {

$startInfo.Verb = "runas" } else {

$startInfo.UserName = $credential.Username $startInfo.Password = $credential.Password $startInfo.UseShellExecute = $false

}

## Start the process [Diagnostics.Process]::Start($startInfo)

Remove a Windows PowerShell User from a Security or Distribution Group

Problem

You want to remove a user from a security or distribution group.

Solution

To remove a user from a security or distribution group, use the [adsi] type shortcut to bind to the group in Active Directory, and then call the Remove() method:

$management = [adsi] "LDAP://localhost:389/cn=Management,ou=West,ou=Sales,dc=Fabrikam,dc=COM"

$user = "LDAP://localhost:389/cn=MyerKen,ou=West,ou=Sales,dc=Fabrikam,dc=COM" $management.Remove($user)

Discussion

The solution removes the MyerKen user from a group named Management in the Sales West OU.

Trim a String in Windows PowerShell

Problem

You want to remove leading or trailing spaces from a string or user input.

Solution

Use the Trim() method of the string to remove all leading and trailing whitespace characters from that string.

PS >$text = " `t Test String`t `t" PS >"|" + $text.Trim() + "|" |Test String|

Discussion

The Trim() method cleans all whitespace from the beginning and end of a string. If you want just one or the other, you can also call the TrimStart() or TrimEnd() method to remove whitespace from the beginning or the end of the string, respectively. If you want to remove specific characters from the beginning or end of a string, the Trim(), TrimStart(), and TrimEnd() methods provide options to support that. To trim a list of specific characters from the end of a string, provide that list to the method, as shown in Example 55.

Example 55. Trimming a list of characters from the end of a string

PS >"Hello World".TrimEnd('d','l','r','o','W',' ') He

At first blush, the following command that attempts to trim the text "World" from the end of a string appears to work incorrectly:

PS >"Hello World".TrimEnd(" World")

He This happens because the TrimEnd() method takes a list of characters to remove from the end of a string. PowerShell automatically converts a string to a list of characters if required, so this command is in fact the same as the command in Example 55.

If you want to replace text anywhere in a string (and not just from the beginning or end)

Convert a VBScript WMI Script to PowerShell

Problem

You want to perform a WMI task in PowerShell, but can find only VBScript examples that demonstrate the solution to the problem.

Solution

To accomplish the task of a script that retrieves data from a computer, use the GetWmiObject cmdlet: foreach($printer in GetWmiObject –Computer COMPUTER Win32_Printer) { ## Work with the properties $printer.Name

} To accomplish the task of a script that calls methods on an instance, use the [Wmi] or [WmiSearcher] accelerators to retrieve the instances, and then call methods on the instances like you would call any other PowerShell method:

$service = [Wmi] 'Win32_Service.Name="winmgmt"'

$service.ChangeStartMode("Manual")

$service.ChangeStartMode("Automatic")

To accomplish the task of a script that calls methods on a class, use the [WmiClass] accelerator to retrieve the class, and then call methods on the class like you would call any other PowerShell method:

$class = [WmiClass] "Win32_Process"

$class.Create("Notepad")

Discussion

For many years, VBScript has been the preferred language that administrators use to access WMI data. Because of that, the vast majority of scripts available in books and on the Internet come written in VBScript.

These scripts usually take one of three forms: retrieving data and accessing properties, calling methods of an instance, and calling methods of a class.

Although most WMI scripts on the Internet accomplish unique tasks, PowerShell supports many of the traditional WMI tasks natively. If you want to translate a WMI example to PowerShell, first check that

there aren’t any PowerShell cmdlets that might accomplish the task directly.

Retrieving data

One of the most common uses of WMI is for data collection and system inventory tasks. A typical VBScript that retrieves data looks like Example 153.

Example 153. Retrieving printer information from WMI using VBScript

strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colInstalledPrinters = objWMIService.ExecQuery _ ("Select * from Win32_Printer")

For Each objPrinter in colInstalledPrinters Wscript.Echo "Name: " & objPrinter.Name Wscript.Echo "Location: " & objPrinter.Location Wscript.Echo "Default: " & objPrinter.Default

Next

The first three lines prepare a WMI connection to a given computer and namespace. The next two lines of code prepare a WMI query that requests all instances of a class. The For Each block loops over all the instances, and the objPrinter.Property statements interact with properties on those instances.

In PowerShell, the GetWmiObject cmdlet takes care of most of that, by retrieving all instances of a class from the computer and namespace that you specify. The first five lines of code then become:

$installedPrinters = GetWmiObject Win32_Printer If you need to specify a different computer, namespace, or query restriction, the GetWmiObject cmdlets supports those through optional parameters.

In PowerShell, the For Each block becomes: foreach($printer in $installedPrinters) { $printer.Name $printer.Location $printer.Default }

Notice that we spend the bulk of the PowerShell conversion of this script showing how to access properties. If you don’t actually need to work with the properties (and only want to display them for reporting purposes), PowerShell’s formatting commands simplify that even further:

GetWmiObject Win32_Printer | FormatList Name,Location,Default

Calling methods on an instance

Although data retrieval scripts form the bulk of WMI management examples, another common task is to call methods of an instance that invoke actions.

For example, Example 154 changes the startup type of a service.

Example 154. Changing the startup type of a service from WMI using VBScript

strComputer = "." Set objWMIService = GetObject("winmgmts:" _

& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colServiceList = objWMIService.ExecQuery _ ("Select * from Win32_Service where StartMode = 'Manual'")

For Each objService in colServiceList

errReturnCode = objService.ChangeStartMode("Disabled") Next

The first three lines prepare a WMI connection to a given computer and namespace. The next two lines of code prepare a WMI query that requests all instances of a class and adds an additional filter (StartMode = 'Manual') to the query. The For Each block loops over all the instances, and the objService.Change(...) statement calls the Change() method on the service.

In PowerShell, the GetWmiObject cmdlet takes care of most of the setup, by retrieving all instances of a class from the computer and namespace that you specify. The first five lines of code then become:

$services = GetWmiObject Win32_Service –Filter "StartMode = 'Manual'" If you need to specify a different computer or namespace, the GetWmiObject cmdlets supports those through optional parameters.

In PowerShell, the For Each block becomes: foreach($service in $services) { $service.ChangeStartMode("Disabled") }

Calling methods on a class

Although less common than calling methods on an instance, it is sometimes helpful to call methods on a WMI class. PowerShell makes this work almost exactly like calling methods on an instance.

For example, a script that creates a process on a remote computer looks like this:

strComputer = "COMPUTER"

Set objWMIService = GetObject _

("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")

objWMIService.Create("notepad.exe")

The first three lines prepare a WMI connection to a given computer and namespace. The final line calls the Create() method on the class.

In PowerShell, the [WmiClass] accelerator lets you easily access WMI classes. The first three lines of code then become:

$processClass = [WmiClass] "\\COMPUTER\Root\Cimv2:Win32_Process"

In PowerShell, calling the method on the class is nearly identical:

$processClass.Create("notepad.exe") 

Windows PowerShell Active Directory

By far, the one thing that makes system administration on the Windows platform most unique is its interaction with Active Directory. As the centralized authorization, authentication, and information store for Windows networks, Active Directory automation forms the core of many enterprise administration tasks.

While PowerShell doesn’t include either Active Directory cmdlets or an Active Directory provider, its access through the .NET Framework provides support for the broad range of Active Directory administration.

Use a COM Object

Problem

You want to create a COM object to interact with its methods and properties.

Solution

Use the NewObject cmdlet (with the –ComObject parameter) to create a COM object from its ProgID. You can then interact with the methods and properties of the COM object as you would any other object in PowerShell.

$object = NewObject ComObject ProgId

For example:

PS >$sapi = NewObject Com Sapi.SpVoice PS >$sapi.Speak("Hello World")

Discussion

Historically, many applications have exposed their scripting and administration interfaces as COM objects. While .NET APIs (and PowerShell cmdlets) are becoming more common, interacting with COM objects is still a common administrative task.

As with classes in the .NET Framework, it is difficult to know what COM objects you can use to help you accomplish your system administration tasks. For a handpicked list of the COM objects most useful to system administrators.

For more information about the NewObject cmdlet, type GetHelp NewObject.

Environmental Awareness in Windows PowerShell

While many of your scripts will be designed to work in isolation, you will often find it helpful to give your script information about its execution environment: its name, current working directory, environment variables, common system paths, and more.

PowerShell offers several ways to get at this information—from its cmdlets, to builtin variables, to features that it offers from the .NET Framework.

Find Event Log Entries with Specific Text

Problem

You want to retrieve all event log entries that contain a given term.

Solution

To find specific event log entries, use the GetEventLog cmdlet to retrieve the items, and then pipe them to the WhereObject cmdlet to filter them, as shown in Example 202.

Example 202. Searching the event log for entries that mention the term “disk”

PS >GetEventLog System | WhereObject { $_.Message match "disk" }

Index Time Type Source EventID Message

2920 May 06 09:18 Info Service Control M... 7036 The Logical Disk... 2919 May 06 09:17 Info Service Control M... 7036 The Logical Disk... 2918 May 06 09:17 Info Service Control M... 7035 The Logical Disk... 2884 May 06 00:28 Erro sr 1 The System Resto... 2333 Apr 03 00:16 Erro Disk 11 The driver detec... 2332 Apr 03 00:16 Erro Disk 11 The driver detec... 2131 Mar 27 13:59 Info Service Control M... 7036 The Logical Disk... 2127 Mar 27 12:48 Info Service Control M... 7036 The Logical Disk... 2126 Mar 27 12:48 Info Service Control M... 7035 The Logical Disk... 2123 Mar 27 12:31 Info Service Control M... 7036 The Logical Disk... 2122 Mar 27 12:29 Info Service Control M... 7036 The Logical Disk... 2121 Mar 27 12:29 Info Service Control M... 7035 The Logical Disk...

Discussion

Since the GetEventLog cmdlet retrieves rich objects that represent event log entries, you can pipe them to the WhereObject cmdlet for equally rich filtering.

By default, PowerShell’s default table formatting displays a summary of event log entries. If you are searching the event log message, however, you are probably interested in seeing more details about the message itself. In this case, use the FormatList cmdlet to format these entries in a more detailed list view. Example 203 shows this view.

Example 203. A detailed list view of an event log entry

PS >GetEventLog System | WhereObject { $_.Message match "disk" } | >> FormatList >>

Index
: 2920

EntryType
: Information

EventID
: 7036

Message
: The Logical Disk Manager Administrative Service servi

ce entered the stopped state.

Category
: (0)

CategoryNumber
: 0

ReplacementStrings : {Logical Disk Manager Administrative Service, stopped

} Source : Service Control Manager TimeGenerated : 5/6/2007 9:18:25 AM TimeWritten : 5/6/2007 9:18:25 AM UserName :

Index : 2919 (...)

For more information about the GetEventLog cmdlet, type GetHelp GetEventLog.

Extend Your Windows PowerShell with Additional Snapins

Problem

You want to use PowerShell cmdlets and providers written by a third party.

Solution

In PowerShell, extensions that contain additional cmdlets and providers are called snapins. The author might distribute them with an automated installer but can also distribute them as a standalone PowerShell assembly. PowerShell identifies each snapin by the filename of its assembly and by the snapin name that its author provides.

To use a snapin:

  1. Obtain the snapin assembly.
  2. Copy it to a secure location on your computer. Since snapins are equivalent to executable programs, pick a location (such as the Program Files directory) that provides users read access but not write access.
  3. Register the snapin. From the directory that contains the snapin assembly, run InstallUtil SnapinFilename.dll. This command lets all users on the computer load and run commands defined by the snapin. You can find the InstallUtil utility in the .NET Framework’s installation directory—commonly C:\WINDOWS\ Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe.
  4. Add the snapin. At a PowerShell prompt (or in your profile file), run the command AddPsSnapin SnapinIdentifier. To see all available snapin identifiers, review the names listed in the output of the command:

GetPsSnapin Registered

5. Use the cmdlets and providers contained in that snapin.

To remove the snapin registration from your system, type InstallUtil /u SnapinFilename.dll. Once uninstalled, you may delete the files associated with the snapin.

Discussion

For interactive use (or in a profile), the AddPsSnapin cmdlet is the most common way to load an individual snapin.

One popular source of additional snapins is the PowerShell Community Extensions project, located at http://www.codeplex.com/PowerShellCX .

Read a Line of User Input in Windows PowerShell

Problem

You want to use input from the user in your script.

Solution

To obtain user input, use the ReadHost cmdlet:

PS >$directory = ReadHost "Enter a directory name" Enter a directory name: C:\MyDirectory PS >$directory C:\MyDirectory

Discussion

The ReadHost cmdlet reads a single line of input from the user. If the input contains sensitive data, the cmdlet supports an –AsSecureString parameter to read this input as a SecureString.

If the user input represents a date, time, or number, be aware that most cultures represent these data types differently.

For more information about the ReadHost cmdlet, type GetHelp ReadHost.