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

PowerShell Workflows: Design Considerations

$
0
0

Summary: Windows PowerShell MVP and Honorary Scripting Guy Richard Siddaway talks about design considerations for Windows PowerShell workflows.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the seventh article in the most excellent Richard Siddaway workflow series. 

Note The first article, PowerShell Workflows: The Basics, introduced the basic concepts of Windows PowerShell workflow. The second article, PowerShell Workflows: Restrictions, discussed the restrictions encountered with working with Windows PowerShell workflows. The third article was PowerShell Workflows: Nesting. The fourth article talked about PowerShell Workflows: Job Engine. The fifth article talked about PowerShell Workflows: Restarting the Computer. Next, was PowerShell Workflow: Using Parameters. You should read these articles before getting into today’s article.

Take it away, Richard …

The first big question you need to ask yourself is: “Should this be a workflow or a normal Windows PowerShell script?”

My fellow MVP Don Jones produced an excellent review of when you should use workflows:

To summarize Don’s conclusions, you should use workflows when:

  • You need to interrupt and restart tasks.
  • You need to checkpoint the task (persistence).
  • You have a mixture of sequential and parallel tasks.

 Some of the other potential reasons for using a workflow include:

  • You need to perform a long-running task that combines multiple steps in a sequence.
  • You need to perform a task that runs on multiple devices.
  • You need to perform a long-running task that is asynchronous.
  • You need to perform a long-running task that is parallelizable.

In reality, these requirements can be met by Windows PowerShell scripts or background Jobs. One of the essentials for being regarded as an expert in a technology is being able to state when it shouldn’t be used, so don’t be afraid to say no to using a workflow!

Parallelization needs to be considered as having two aspects:

  1. Simultaneously running the same tasks on a number of machines.
  2. Simultaneously running a number of tasks on a single machine.

In the first case, you could use Jobs or workflows. In the second case, you are definitely in workflow territory. There is a grey area. Imagine the case when you need to run a task on a large scale, or in high availability environments, that potentially requires throttling and connection pooling. This situation may be best suited to a Windows PowerShell Job or a workflow. There is no definitive answer either way. I know of organizations that have run Windows PowerShell Jobs that touched thousands of machines. Workflows are still too new for us to have a body of evidence to produce guidelines—if you’ve run large-scale workflows, I’d be interested in hearing your experiences, if you can share them.

One last thought before we start to look at some examples—even given that you could use a Windows PowerShell Job—there is nothing wrong with using a workflow to get experience. The only way to learn these techniques is to use them.

Let’s have a look at some examples. One common scenario is to add a registry key to a number of remote computers. Many organizations have a heterogeneous environment. I’ll simulate that by using these machines.

Machine

Operating system

Windows PowerShell version

W12SUS

Windows Server 2012

Windows PowerShell 3.0

Server02

Windows Server 2012

Windows PowerShell 3.0

WebR201

Windows Server 2008 R2

Windows PowerShell 2.0

Win7test

Windows 7

Windows PowerShell 3.0

Creating a registry key and value is a job for WMI—you didn’t think I wouldn’t touch on it, did you? One of my machines uses Windows PowerShell 2.0, so I need to use the WMI cmdlets rather than the CIM cmdlets from Windows PowerShell 3.0, because the default connection for CIM cmdlets is WSMAN, but it has to be WSMAN v3.

Adding the key involves these steps:

$hklm =  2147483650

$key = "SOFTWARE\HSGworkflowDEMO"

Invoke-WmiMethod -Class StdRegProv -Name CreateKey -ArgumentList $hklm, $key

You need these extra steps to add a value:

$value = "AreYouThere"

$data = "Yes"

Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $hklm, $key, $data, $value

In case you are wondering about the order of the parameters, I checked this with Get-CimClass:

$class = Get-CimClass -ClassName StdRegProv

$class.CimClassMethods["SetStringValue"]

$class.CimClassMethods["SetStringValue"].Parameters

This is what was returned:

Name        CimType Qualifiers

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

hDefKey      UInt32 {ID, IN}

sSubKeyName  String {ID, IN}

sValue       String {ID, in}

sValueName   String {ID, in}

If you look at the documentation, it states the sValueName should occur beforesValue, which makes sense, but Invoke-WmiMethod has an issue with parameters and seems to expect them in alphabetical order! If in doubt, use the order that Get-CimClass reports.

This demonstrates what is, probably, the most important point regarding the creation of workflows: test your code outside of the workflow. If you know your code works, it makes testing the workflow easier. Using this for a local machine is a simple script. If you want to run it remotely, you have a couple options:

  1. Use the –ComputerName parameter with Invoke-WmiMethod for each call.
  2. Wrap the commands in a script block and run remotely through Invoke-Command.

In either case, you can use the –AsJob parameter.

We’ll go with option 2.

$sb = {

$hklm =  2147483650

$key = "SOFTWARE\HSGworkflowDEMO"

Invoke-WmiMethod -Class StdRegProv -Name CreateKey -ArgumentList $hklm, $key

$value = "AreYouThere"

$data = "Yes"

Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $hklm, $key, $data, $value

}

 Invoke-Command -ComputerName server02, w12sus, win7test, webr201 -ScriptBlock $sb

This could be run as a Windows PowerShell Job by adding the –AsJob parameter, which changes the last line to the following:

Invoke-Command -ComputerName server02, w12sus, win7test, webr201 -ScriptBlock $sb -AsJob

As a side note, I noticed that the commands on machines with Windows PowerShell 3.0 executed more quickly than the machine with Windows PowerShell 2.0.

To turn this into a workflow involves changing the code to look like this:

workflow new-regkey {

$hklm =  2147483650

$key = "SOFTWARE\HSGworkflowDEMO"

Invoke-WmiMethod -Class StdRegProv -Name CreateKey -ArgumentList $hklm, $key 

$value = "AreYouThere"

$data = "Yes"

Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $hklm, $key, $data, $value

}

And we run the workflow by using the –PSComputerName parameter:

new-regkey -PSComputerName server02, w12sus, win7test, webr201

So far, we have a simple task that could be accomplished in a number of ways. Let’s complicate things a bit. Your task becomes:

  • Create a registry key:  HKLM:\SOFTWARE\HSGworkflowDEMO
  • Create three values in that key and populate each with a given value:
    • AreYouThere = Yes
    • AreWeThereYet = No
    • PowerShell = 1

This gives you a mixture of parallel and sequential tasks. You have to create the key before the values, but you can create the values in parallel. You could code this as a Windows PowerShell script, and it would work perfectly but sequentially. Using a workflow means you can do things in parallel, which may be more efficient. One solution to creating a workflow to solve this problem looks like this:

workflow new-regkey {

$hklm =  2147483650

$key = "SOFTWARE\HSGworkflowDEMO"

Invoke-WmiMethod -Class StdRegProv -Name CreateKey -ArgumentList $hklm, $key

parallel {

 sequence {

  $value = "AreYouThere"

  $data = "Yes"

  Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $hklm, $key, $data, $value

 } # end of sequence

 sequence {

  $value = "PowerShell"

  $data = 1

  Invoke-WmiMethod -Class StdRegProv -Name SetDwordValue -ArgumentList $hklm, $key, $value, $data

 } # end of sequence

 sequence {

  $value = "AreWeThereYet"

  $data = "No"

  Invoke-WmiMethod -Class StdRegProv -Name SetStringValue -ArgumentList $hklm, $key, $data, $value

 }# end of sequence

}# end of parallel

}

Run the workflow:

new-regkey -PSComputerName server02, w12sus, win7test, webr201

Notice the order of arguments changes for Invoke-WmiMethod in the second sequence block. One of the joys of WMI. I really like the way you can use the –PSComputername parameter in the workflow, and it automatically passes the computer names to the workflow activities.

The workflow breaks down like this:

  • Sequentially:
    • Create the registry key
    • In parallel create three registry values—two strings and one integer
      • The creation of each value consists of three sequential steps

This is where the fun, headaches, and skill come into designing your workflows—deciding what can run in parallel, what has to run sequentially, what variables are needed, and identifying any scope issues.

As with any major piece of work, I recommend sitting down and designing your workflow before you start coding. I covered how I go about creating a script to solve a problem in my commentary on the 2012 Scripting Games.

For workflows, your decision making needs to cover:

  • What tasks is the workflow performing?
  • In what order do those tasks occur?
  • What can be performed in parallel?
  • Do I need any InLineScript blocks?
  • What variables do I need and where are they defined?
    • What is the impact of workflow scope?
  • Does the workflow run against remote computers?
    • Does it run on the local machine and access remote computers or run through remoting so that it is running on the remote computer?
    • Where do I define the computer name parameters—workflow or activity or cmdlet?
  • What do I want returned by the workflow?
  • Is there a computer restart involved in the workflow?
  • Do I need to checkpoint the workflow?
    • If so, how often?
  • Do I need to store output data outside the workflow?
  • Will the workflow run as a Job?

You’ve seen how to perform these tasks in previous workflows. How the pieces go together depends on the problem you are trying to solve.

In case you were wondering how to remove the registry keys you’ve created:

$sb = {

$hklm =  2147483650

$key = "SOFTWARE\HSGworkflowDEMO"

Invoke-WmiMethod -Class StdRegProv -Name DeleteKey -ArgumentList $hklm, $key

}

Invoke-Command -ComputerName server02, w12sus, win7test, webr201 -ScriptBlock $sb

Deleting the key removes the key, any sub-keys, and any values.

This installment has been a bit wordier than the others, but normal service will be resumed next time when you get a big workflow to consider, which incorporates a lot of what we’ve covered in the series.

~Richard

Thank you, Richard. This series is most excellent, and I really appreciate you taking the time to share your knowledge with the Windows PowerShell scripting community.

Join me tomorrow when I will talk about more cool Windows PowerShell stuff.

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

Ed Wilson, Microsoft Scripting Guy


PowerTip: Use a PowerShell Cmdlet to Find Group Membership

$
0
0

Summary: Learn how to use a Windows PowerShell cmdlet to find group membership.

Hey, Scripting Guy! Question I want to use Windows PowerShell to find all members of a particular group in Active Directory. I would like to do this even if the membership is through other groups (indirect). What can I do?

Hey, Scripting Guy! Answer Use the Get-ADGroupMember cmdlet from the ActiveDirectory module with the –Recursive parameter. The following illustrates this technique.

Get-ADGroupMember -Identity 'Domain Admins' -Recursive

Use PowerShell to Generate and Parse a Group Policy Object Report

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to generate and parse a Group Policy Object report.

Microsoft Scripting Guy, Ed Wilson, is here. So, I am working on my MCSE: Server Infrastructure certification and having passed the Installing and Configuring Windows Server 2012 exam (70-410), I am now studying for the Administering Windows Server 2012 exam (70-411). According to the 70-411 exam guidelines, 15 to 20 percent of the exam is related to Configuring and Managing Group Policy.

Note  I have been taking Microsoft Certification exams since my first MCSE (on Windows NT 3.51). The one big tip I can give you is to adhere to the Skills Being Measured (study guide). If you see something on the list that you do not understand, look it up on TechNet, search the online Help, and actually perform the task. Luckily, now with Hyper-V, this is much easier. You can learn a lot by clicking through a wizard—much more than just looking at screenshots.

Anyway, I am spending a lot of time here lately playing around with Group Policy.

Generating a RSOP report

Nearly everyone who has spent any time as a network administrator, or even a Help desk person, has run into situations that demand analyzing Resultant Set of Policy (RSoP). The dreaded situation arises where it seems settings are not being applied that should be applied or settings are applied that should not be applied. The standard tool for troubleshooting this is the GPRESULT command. The syntax of this command always seems a bit confusing, but eventually, I come up with a command that works. This command is shown here.

gpresult /r

Outputting the RSoP report to the Windows PowerShell console does not make for a great troubleshooting experience—everything goes to the console, and this results in a lot of scrolling up and down. This is shown here.

Image of command output

One thing that can be done to improve this experience is to output the report to Out-GridView. This is shown here.

gpresult /r | Out-GridView

Even though the output is just plain text, it does not preclude using the Out-GridView cmdlet to parse the results.

By typing in the filter box on top of the GridView pane, I can filter the text that appears in the bottom. This is a good way to bring some measure of control to the output.

Image of filter box UI

XML is a better way to filter the output

By using the Windows PowerShell cmdlet Get-GPOReport (from the GroupPolicy module from the RSAT tools), I can gain a bit of flexibility as I dive into a specific Group Policy Object. The Get-GPOReport cmdlet will produce two different types of reports—HTML or XML. The cmdlet also has a –path parameter that I use to specify the path for storing the report. Therefore, to produce a report about the BackupOnLogOff GPO that exists in my domain, I use the following syntax:

Get-GPOReport -Name backuponlogoff -ReportType html -Path c:\fso\backup.html

I can open the produced report in Internet Explorer. The report is shown here.

Image of report

Of course, generating a report to an HTML file does not help me too much—in fact, it is not much different than writing the output to the Out-GridView cmdlet.

However, if I do not specify a path, the output returns to the console. Now, obviously, I do not want to write HTML to the Windows PowerShell console; in fact, I do not even want to write XML to the Windows PowerShell console, but what if I store the results in a variable and cast the variable to an XML document? Hmm … might that work?

Well, as a matter of a fact, it does. Here is the command I use to get a GPO report in XML and store it in a variable as an XMLDocument object.

[xml]$xml = Get-GPOReport -Name backuponlogoff -ReportType xml

Note  Keep in mind when exploring the XML document that tab expansion works, and so there is actually very little typing required.

The main property I like to begin with is the DocumentElement. From this property, I see several objects I can explore. This is shown here.

13:01 C:\> $xml.DocumentElement

xsd                 : http://www.w3.org/2001/XMLSchema

xsi                 : http://www.w3.org/2001/XMLSchema-instance

xmlns               : http://www.microsoft.com/GroupPolicy/Settings

Identifier          : Identifier

Name                : BackupOnLogOff

IncludeComments     : true

CreatedTime         : 2012-02-22T19:19:53

ModifiedTime        : 2012-02-22T19:19:53

ReadTime            : 2013-02-05T18:01:33.4004324Z

SecurityDescriptor  : SecurityDescriptor

FilterDataAvailable : true

Computer            : Computer

User                : User

LinksTo             : LinksTo

To see if the GPO is enabled on the Computer or on the User portion, I examine the enabled property from those objects, as shown here.

13:01 C:\> $xml.DocumentElement.Computer

VersionDirectory             VersionSysvol               Enabled

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

0                            0                           true

The following image shows the commands and the associated output used to obtain the XML report and to parse it.

Image of command output

If I want to see the owner, I navigate through the SecurityDescriptor object, as shown here.

13:01 C:\> $xml.DocumentElement.SecurityDescriptor.Owner

xmlns                        SID                         Name

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

http://www.microsoft.com/... SID                         Name

 

13:01 C:\> $xml.DocumentElement.SecurityDescriptor.Owner.Name

xmlns                                      #text

-----                                      -----

http://www.microsoft.com/GroupPolicy/Types IAMMRED\Domain Admins

I can also easily see where the GPO links by examining the LinksTo object, as shown here.

13:02 C:\> $xml.DocumentElement.LinksTo

SOMName               SOMPath              Enabled              NoOverride

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

Charlotte             iammred.net/Charl... true                 false

Well, that is about it for looking at a GPO report in XML. Join me tomorrow when I will talk about more cool Group Policy stuff.

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

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell to Get a Listing of All GPOs in a Domain

$
0
0

Summary: Use a Windows PowerShell cmdlet from the RSAT tools to display all GPOs defined in a domain.

Hey, Scripting Guy! Question How can I get a listing of all the GPOs defined in my domain?

Hey, Scripting Guy! Answer Use the Get-GPO cmdlet from the RSAT tools. Instead of specifying a GPO name or GUID, use the –all switch, as shown here.

Get-GPO -all

Use PowerShell to Find Group Policy RSoP Data

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to generate a Group Policy Resultant Set of Policy (RSoP) report.

Microsoft Scripting Guy, Ed Wilson, is here. I can tell you that spring is definitely heating up. Not necessarily temperature-wise, but in the world of Windows PowerShell. We have the MVP summit coming up in Redmond in which Microsoft MVPs from all over the world will converge on Redmond to make and to reinforce contacts with both the product groups and with themselves. This year, there is an awesome group of presentations about Windows PowerShell for the PowerShell MVPs.

Following that, there is the Windows PowerShell Summit in Redmond. The Scripting Wife and I will be there (I am making three presentations). This is a sold out international event, with Windows PowerShell people coming from all over the world to partake of three days of Windows PowerShell goodness. It will be awesome.

Then, there are the 2013 Scripting Games, and TechEd 2013 in New Orleans, not to mention countless community events cropping up all over the place (such as PowerShell Saturday). In the midst of that, I know I am doing several web casts as well. In fact, I just signed up to do one for O’Reilly in celebration of my new Microsoft Press Windows PowerShell 3.0 Step by Step book, and I know there are at least three remote Windows PowerShell user group meetings in the works. In fact, as I am writing this, I just got the meeting invite to appear on the PowerScripting Podcast on February 21, 2013, at 9:30 P.M. Eastern Standard Time. The PowerScripting Podcast is always fun. I especially enjoy the interaction in the chat room during the recording of the show. Dude, Dude, Dude: I think that sometimes people deliberately say stuff to make me start laughing.

Generate an XML RSoP report

As I mentioned yesterday, it is possible to generate an RSoP report for all of the Group Policy Objects by using the GPResult command. My biggest problem with the GPResult command is actually the syntax. The syntax is not consistent, and the online Help examples do not easily clarify the usage. But, that is not the case with Windows PowerShell. The great thing about Windows PowerShell is that the usage is consistent across cmdlets, and if I miss a required parameter, Windows PowerShell prompts for the missing parameter instead of erroring out, timing out, or going into la-la land.

Admin rights required

Keep in mind, that to run the Get-GPResultantSetOfPolicy cmdlet, you must start Windows PowerShell with elevated rights. Unfortunately, the generated error does not appear to have anything to do with admin rights. The error, appearing in a non-elevated Windows PowerShell console, is shown in the following image.

Image of error output

So, how did I figure out I needed to run Windows PowerShell with elevated rights? Well, I actually wrote a book on WMI, and I recognize HRESULT: 0x80041003 as an old-fashioned WMI access denied error. WMI did I say? Yes, indeed. RSoP data is actually stored in the WMI repository in the Root/RSoP namespace. Therefore, receiving a WMI error message is not completely unexpected.

When I open the Windows PowerShell console with admin rights, I import the module and call the command to produce an XML report. In yesterday’s article, I used the Get-GPOReport cmdlet to analyze a specific GPO.

The Get-GPOReport cmdlet returns the XML directly to the Windows PowerShell console if I do not supply a path. I used that technique to capture the XML in a variable and to process the XML as an XMLDocument. Unfortunately, this is not an option with the Get-GPResultantSetOfPolicy cmdlet, and therefore, it is required to specify the path as well. The following command prompts for the path.

Get-GPResultantSetOfPolicy -ReportType xml

The command and the prompt for the path as well as the results of the command once the path is supplied are shown here.

Image of command output

Parse the XML report

The cool thing is that after the XML report generates, there is no longer a need for the elevated Windows PowerShell console. I can open the XML report in a non-elevated Windows PowerShell console, convert the content to an XML document, and parse it as required. This technique is shown here.

Image of command output

One of the things I need to know is the link order of the GPOs. This is because, in general, GPOs applied last take precedence. So, for example, I could enable something in one GPO, disable it in the second GPO, and then enable it back again in another GPO. Depending on the order in which these GPOs are applied will determine, when we look at RSOP, if the policy is enabled or disabled. The following command parses the RSOP report and chooses the name of the GPO and the link order.

14:27 C:\> $xml.DocumentElement.ComputerResults.GPO | select name, @{LABEL="LinkOrder

";EXPRESSION={$_.link.linkorder}} | sort linkorder

 Name                                       LinkOrder

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

Local Group Policy                         1

EnablePSRemoting                           2

Enable WMI Relibility                      3

Default Domain Policy                      4

BackupOnLogOff                             5

Join me tomorrow when I will talk about exploring the Windows PowerShell RunSpace and the associated apartment mode.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Easily Find the PowerShell ApartmentState

$
0
0

Summary: Easily find the Windows PowerShell ApartmentState.

Hey, Scripting Guy! Question How can I easily find the Windows PowerShell ApartmentState?

Hey, Scripting Guy! Answer Use the $host automatic variable, and select the ApartmentState property from the RunSpace object, as shown here.

PS C:\> $Host.RunSpace.ApartmentState

STA

Weekend Scripter: Playing Around with PowerShell Namespace ApartmentState

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about exploring the Windows PowerShell namespace ApartmentState property.

Microsoft Scripting Guy, Ed Wilson, is here. Ah, it is the weekend. It is not that I “live for weekends” or anything like that. It is just that weekends are different. I generally spend more time preparing my breakfast—Irish steel-cut oats are not out of the question (and it takes me a good 45 minutes to cool steel-cut oats). I take extra care in preparing just the right pot of tea to suit my mood, and I spend long hours playing around with Windows PowerShell—often with no preconceived results in mind. I just want to play, explore, and learn. During the week, I always have a plan—I know what the next 20 Hey, Scripting Guy! Blog posts will be. I always work towards a plan. No so on the weekend. 

Windows PowerShell 3.0 console

Did you know that in Windows PowerShell 3.0, we changed the Windows PowerShell console from multi-threaded apartment (MTA) to single-threaded apartment (STA)? If you did not notice the change, you are probably not doing anything that requires MTA mode. If all of a sudden, you have some Windows PowerShell 2.0 or even Windows PowerShell 1.0 scripts that no longer work, now you know why.

Note  If you need a refresher on apartment models, refer to Apartments and Pumping in the CLRon Chris Brumme’s blog.

The Windows PowerShell 2.0 defaults were the Windows PowerShell ISE opened in STA mode, and the Windows PowerShell console opened in MTA mode. In Windows PowerShell 3.0, both the Windows PowerShell console and the Windows PowerShell ISE open in STA mode. By returning the Runspace property from the $host automatic variable, I obtained useful information about the Runspace. The following command shows this.

$host.Runspace

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

Image of command output

I can pick up the host name as well as the ApartmentState directly. This technique is shown here.

15:42 C:\> $host.name

ConsoleHost

15:42 C:\> $host.Runspace.ApartmentState

STA

In the Windows PowerShell ISE, the following results are shown.

PS C:\> $host.Name

Windows PowerShell ISE Host

 

PS C:\> $host.Runspace.ApartmentState

STA

The Runspaceproperty returns a LocalRunSpace object. The members of this object are shown here.

15:45 C:\> $host.Runspace | gm

   TypeName: System.Management.Automation.Runspaces.LocalRunspace

Name                         MemberType Definition

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

AvailabilityChanged          Event      System.EventHandler`1[System.Management.A...

StateChanged                 Event      System.EventHandler`1[System.Management.A...

ClearBaseTransaction         Method     void ClearBaseTransaction()

Close                        Method     void Close()

CloseAsync                   Method     void CloseAsync()

Connect                      Method     void Connect()

ConnectAsync                 Method     void ConnectAsync()

CreateDisconnectedPipeline   Method     System.Management.Automation.Runspaces.Pi...

CreateDisconnectedPowerShell Method     powershell CreateDisconnectedPowerShell()

CreateNestedPipeline         Method     System.Management.Automation.Runspaces.Pi...

CreatePipeline               Method     System.Management.Automation.Runspaces.Pi...

Disconnect                   Method     void Disconnect()

DisconnectAsync              Method     void DisconnectAsync()

Dispose                      Method     void Dispose(), void IDisposable.Dispose()

Equals                       Method     bool Equals(System.Object obj)

GetApplicationPrivateData    Method     psprimitivedictionary GetApplicationPriva...

GetCapabilities              Method     System.Management.Automation.Runspaces.Ru...

GetHashCode                  Method     int GetHashCode()

GetType                      Method     type GetType()

Open                         Method     void Open()

OpenAsync                    Method     void OpenAsync()

ResetRunspaceState           Method     void ResetRunspaceState()

SetBaseTransaction           Method     void SetBaseTransaction(System.Transactio...

ToString                     Method     string ToString()

ApartmentState               Property   System.Threading.ApartmentState Apartment...

ConnectionInfo               Property   System.Management.Automation.Runspaces.Ru...

Debugger                     Property   System.Management.Automation.Debugger Deb...

Events                       Property   System.Management.Automation.PSEventManag...

InitialSessionState          Property   System.Management.Automation.Runspaces.In...

InstanceId                   Property   guid InstanceId {get;}

JobManager                   Property   System.Management.Automation.JobManager J...

LanguageMode                 Property   System.Management.Automation.PSLanguageMo...

OriginalConnectionInfo       Property   System.Management.Automation.Runspaces.Ru...

RunspaceAvailability         Property   System.Management.Automation.Runspaces.Ru...

RunspaceConfiguration        Property   System.Management.Automation.Runspaces.Ru...

RunspaceStateInfo            Property   System.Management.Automation.Runspaces.Ru...

SessionStateProxy            Property   System.Management.Automation.Runspaces.Se...

ThreadOptions                Property   System.Management.Automation.Runspaces.PS...

Version                      Property   version Version {get;}

The Runspace object is documented on MSDN. This means, that if I wish, I can access the same information via the .NET Framework. This technique is shown here.

15:48 C:\> [System.Management.Automation.Runspaces.runspace]::DefaultRunspace.Apartme

ntState

STA

Hey, I don’t know about you, but for me, I prefer to access the information via $host (although keep in mind that tab expansion does work for these types of things now).

Oh well, I am off to do some other stuff on this fine PowerShell day. Join me tomorrow for an article from the Scripting Wife about the Winter 2013 Scripting Games. Yes, if you don’t know, they are going on right now.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use PowerShell to Find GPOs Without a Description

$
0
0

Summary: Learn how to use Windows PowerShell to find GPOs without a description.

Hey, Scripting Guy! Question How can I use Windows PowerShell to assist in detecting GPOs that do not have a description?

Hey, Scripting Guy! Answer Use the Get-GPO cmdlet with the –all switch. Pipe the results to the Where-Object and look for an absence of the description property, as shown here.

14:38 C:\> Get-GPO -all | where {-not $_.description} | select displayname, description

 DisplayName                                Description

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

wsus

Default Domain Policy

Default Domain Controllers Policy

Enable WMI Relibility

EnablePSRemoting

BackupOnLogOff

 

 


Weekend Scripter: The Scripting Wife Talks About the First Warm-Up Event in the Winter Scripting Games

$
0
0

Summary: The Scripting Wife talks about the first event in the 2013 Winter Scripting Games warm-up events.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have a special guest. You guessed it: The Scripting Wife is with us today. If you are reading the Hey, Scripting Guy! Blog for the first time, Welcome.

Let me give you a little background. My real-life wife, Teresa, is not an IT person—she is an accounting kind of lady. However, over the years, she has gotten involved in the Windows PowerShell community and is a tremendous asset, if I do say so myself. Teresa thought the Scripting Games sounded like fun and joined in a couple of years ago. This turned out to be a fun way to use Teresa as an example for how you too can learn Windows PowerShell. There are several blogs that we have written where Teresa has been the student, and I have conducted training scenarios with her. Now that you are up to speed I will turn the keyboard over to Teresa.

Hello everyone, thank you for joining us today. I am so happy to see the Winter Scripting Games Camp warm-up events. The Competitor Guide is available from the PowerShell.Org web site. I have completed the first scenario and wanted to share with you how I solved the scenario just in case you did not have time to complete the task.

The first task is now over, but the second task opened on February 8, 2013. Part of the purpose for these events is to test the new Scripting Games platform, as well as to give the PowerShell.Org community some first-hand experience in running the games. The Scripting Games (as you may know) has been turned over to the Windows PowerShell community. (See the announcement on the Scripting Guys blog, as well as on the PowerShell.Org website.)

The task for this first event was to display the percentage of free space (in two digits) for my disk drive.

Whenever I think about doing stuff with the disk drive, I think about using WMI. My first choice is Win32_Volume.

I did a search on the Hey, Scripting Guy! Blog and came up with this hit that talked about using format specifiers to control the way that the information displays to the Windows PowerShell console. I also found a good Hey, Scripting Guy! Blog post called Use PowerShell to Create a Report Displaying Free Disk Spacethat basically contains the entire solution to the problem. When I saw that, I decided to not look at it yet. I decided to go back and review a Scripting Wife article where I talk about creating custom table headings with Out-GridView.

After I had spent some time reviewing the articles, and making sure that I do not cheat, I decided to take a stab at it (ok, several stabs). The first part is pretty straightforward—I know I need to use Get-WmiObject to query WMI. I know I want to use the Win32_Volume WMI class. I also know that gwmi is an alias for the Get-WmiObject cmdlet, as shown here.

gwmi win32_volume

The hard part is to display the percentage of free disk space in only two decimal places. This is where the format specifiers come into play … and they do not play very nicely. If I want to format a number with two decimal places, I first define the pattern. This pattern goes inside a pair of quotation marks and inside a pair of curly brackets. The other part I really do not understand—I just copied it and it works.

Now, I do know that I use –f, which is the format operator. This is really just one of those things I need to know how to find it, so I have a pattern to do what I need to do. I can’t memorize it very well, because I do not do it all that often—but hey, as long as I can find the Scripting Guy blog and search for it, then I can find what I need.

If I want to display the number 123.00345 as two decimal places, I would use the following code.

17:37 C:\> "{0:N2}" -f 123.00345

123.00

Now, for another thing that is not very straightforward, but at least it is well-documented in Help—creating a custom table label and computed number. We know about the two percentage places, and now we need to divide and multiply to get the percent free space. I divide freespace by capacity, and multiply by 100. To get it into a custom table, I specify Label and Expression. The Label is a string that becomes the Column heading. The expression is the script block that figures out the free space and also formats the output in two decimal places. The event required a one-liner. The following is a single logical line I broke up for readability purposes.

gwmi win32_volume |

ft DriveLetter, FreeSpace, Capacity,

@{Label="PercentFree";Expression= {"{0:N2}" -f (($_.freespace / $_.capacity) * 100)}}

The one line of code and the output associated with the code are shown in the following image.

 

I could use the same custom table property technique to change the display of free space and capacity to display in gigabytes instead of bytes. That would be a decent amount of work, however, and it was not required by the event.

I hope you find my description useful. When the second event ends, I will publish my solution to it as well. I am not certain that my code is what they were looking for, but it does meet the requirements. Hope you have a great weekend.

Thank you, Scripting Wife—nice write up. Join me tomorrow for more cool Windows PowerShell stuff.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use PowerShell to Obtain Permissions Report for a GPO

$
0
0

Summary: Learn how to use Windows PowerShell to obtain a permissions report for a specific Group Policy Object (GPO).

Hey, Scripting Guy! Question How can I use Windows PowerShell to obtain a report of permissions for a specific Group Policy Object (GPO)?

Hey, Scripting Guy! Answer Use the Get-GPPermission cmdlet, specify the name of the GPO and use –all to obtain permissions for all users, as shown here.

Get-GPPermission –name backuponlogoff -all

Clean Up Your PowerShell History to Remove Error Entries

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, shows how to remove all error commands from your Windows PowerShell command history.

Microsoft Scripting Guy, Ed Wilson, is here. One of the great things about being involved with a Windows PowerShell user group is all the great people I meet and all the cool ideas that arise. It seems that every time I attend a Windows PowerShell user group meeting, I come away with at least one idea for a Hey, Scripting Guy! Blog post. (The Charlotte PowerShell User Group meets on the first Thursday of the month at the Microsoft office in Charlotte, North Carolina, in the United States.)

Note You can see if there is a Windows PowerShell user group that meets in your area by going to the PowerShellGroup.org webpage. If you would like to start a Windows PowerShell user group in your area, you may want to take a look at these Hey, Scripting Guy! Blog posts for ideas on how to start.

Image of NYC Windows PowerShell user group logo

 

Double Note  Tonight, February 11, 2013, at 6:00 P.M. in New York City is the NYC Windows PowerShell User Group meeting. They will be sharing their favorite Windows PowerShell tips and tricks, and I will publish them on the Hey, Scripting Guy! Blog. This will be cool. If you are anywhere near the Sixth Avenue Microsoft office, you should check it out. You can register (for free) via the link above.

 

 

Examining the error object

Anyway, Microsoft PowerShell MVP Jim Christopher (the Charlotte Windows PowerShell user group president) created a lab using a way cool SQLite provider he wrote. This turned into a great hands-on lab for working with providers. During the course of the wrap up and summary, I suggested we look though the $error object and commented on the fact that if you pipe the object to Format-List * and use the –FORCE parameter, additional details appear. 

An example of this technique is shown using $error[0], which is always the most recent error generated.

$error[0] | Format-List * -Force

The command and its associated output are shown in the following figure.

Image of command output

So, the InvocationInfoproperty contains an object. I can access the object directly, as shown here.

$Error[0].InvocationInfo

The command and the output associated with the command are shown here.

Image of command output

Now, if I use the Get-History cmdlet to display my command history, I can immediately see several errors are retained in my history. This makes it a real pain when I am using the Up Arrow key through a series of commands. The Get-History command and the associated output are shown here.

Image of command output

Ok, now I clear the history entries that have errors

To clear all of the error-causing commands from my command history, I first pipe the $error object to the Foreach-Object cmdlet (% is an alias for the Foreach-Object cmdlet). Inside the script block for the Foreach-Object cmdlet, I pipe the history IDs to the Where-Object (? is an alias for the Where-Object) where I look for a history ID that is greater than 0.

Once I find those, I pipe the history IDs once again to a Foreach-Object cmdlet (still using % as an alias). Inside the Foreach-Object cmdlet, I use the Clear-History cmdlet to remove the error-inducing commands from my history stack. The complete command is shown here.

$Error | % { $_.invocationinfo.historyid} | ? {$_ -gt 0} | % {clear-history -id $_}

Now when I check my command history, I see that that most of the offending commands were successfully removed from the history. This is shown in the following image.

Image of command output

But, some of the errors—those related to calling a static method on a non-existent type—did not get deleted. The reason is that those types of errors do not write the proper history index—instead they show up with a -1 for the HistoryID, as shown here.

15:06 C:\> $Error[0].InvocationInfo

MyCommand             :

BoundParameters       : {}

UnboundArguments      : {}

ScriptLineNumber      : 1

OffsetInLine          : 1

HistoryId             : -1

ScriptName            :

Line                  : [int8]::minvalue

PositionMessage       : At line:1 char:1

                        + [int8]::minvalue

                        + ~~~~~~~~~~~~~~~~

PSScriptRoot          :

PSCommandPath         :

InvocationName        :

PipelineLength        : 0

PipelinePosition      : 0

ExpectingInput        : False

CommandOrigin         : Internal

DisplayScriptPosition :

Still, this is pretty useful, and I would not have even come up with the idea if I had not attended the Windows PowerShell user group meeting the other night in Charlotte. In fact, I am thinking about putting this into a function called Remove-BadHistoryEntry and placing it in my profile. In fact, I just wrote the Remove-BadHistoryEntry function and uploaded it to the Scripting Guys Script Repository. Check it out. It is cool.

Join me tomorrow when I will talk about more way cool Windows PowerShell stuff.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use PowerShell to Reveal the Size of an [int16]

$
0
0

Summary: Use Windows PowerShell to show the minimum value and the maximum value of an [int16].

Hey, Scripting Guy! Question I need to use an [int16], but I am not sure if it is big enough to hold the number I want to use. How can I determine the minimum and the maximum value of an [int16]?

Hey, Scripting Guy! Answer Use the static MinValue and MaxValue properties from the [int16], as shown here.

13:11 C:\> [int16]::MaxValue

32767

13:11 C:\> [int16]::MinValue

-32768

Note This technique also works for [int32], [int64], and [double].

Learn How to Easily Troubleshoot PowerShell Remoting

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, shows you how to easily troubleshoot Windows PowerShell remoting.

Hey, Scripting Guy! Question Hey, Scripting Guy! I have a problem. I am running Windows 8 on my desktop, and I have upgraded our Hyper-V servers to Windows Server 2012. For those computers, I can easily use Windows PowerShell remoting and I have not seen a single problem. But when I attempt to connect to our servers running Windows Server 2008 R2, and even the Windows Server 2008 servers, then it seems to be hit or miss. I need an easy way to troubleshoot this without doing a lot of special logging and going all extreme. Can you help?

—KG

Hey, Scripting Guy! Answer Hello KG,

Microsoft Scripting Guy, Ed Wilson, is here. Well, today I am all in a dither (or a tither, not sure which). I just got a picture of Dr. Scripto through the email—no, no one has kidnapped him (á la Who’s Harry Crumb), no, the picture is from my friend Kurt Hudson, who is a senior technical writer on the Information Experience team. You see, he and I were in meetings together a few weeks ago when I was in Redmond, and he mentioned his hobby—welding. I suggested he might try to weld a figure of Dr. Scripto—he not only accepted the challenge, as you can see in the image here, he excelled.

Image of Dr. Scripto sculpture

Well, KG, as it turns out, I was reviewing a chapter from my Windows PowerShell 3.0 First Steps book by Microsoft Press, and it seems that is the best way for me to answer your question—without the need to reinvent the wheel.

So, here goes …

Troubleshooting Windows PowerShell Remoting

The first tool to use to see if Windows PowerShell remoting is working (or not) is the Test-WSMan cmdlet. Use it first on the local computer (no parameters are required). The command and its associated output are shown here.

PS C:\> Test-WSMan

wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor   : Microsoft Corporation
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 3.0

To test a remote computer, specify the –ComputerNameparameter. This following command runs against a Windows Server 2012 domain controller named DC3.

PS C:\> Test-WSMan -ComputerName dc3

wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor   : Microsoft Corporation
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 3.0

But the Test-WSMan cmdlet also works against a computer running Windows PowerShell 2.0. The following command runs against a Windows Server 2008 domain controller named DC1.

PS C:\> Test-WSMan -ComputerName dc1

wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor   : Microsoft Corporation
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 2.0

To examine a specific Windows PowerShell session, use the Get-PSSession cmdlet. The easiest way to do this is to pipe the variable containing the Windows PowerShell session to the Get-PSSession cmdlet. The key items to pay attention to are the computer name, the state of the session, and the availability of the session. This technique is shown here.

PS C:\> $ps | Get-PSSession

 Id Name            ComputerName    State         ConfigurationName     Availability
 -- ----            ------------    -----         -----------------     ------------
  3 Session3        ex1             Opened        Microsoft.PowerShell     Available
  4 Session4        dc3             Opened        Microsoft.PowerShell     Available

To focus on a specific session, reference the session by either ID or by Name. Send the returned session object over the pipeline to the Format-List cmdlet and select all of the properties. This technique is shown here (using fl as an alias for the Format-List cmdlet).

PS C:\> Get-PSSession -Name Session4 | fl *

State                  : Opened
IdleTimeout            : 7200000
OutputBufferingMode    : None
ComputerName           : dc3
ConfigurationName      : Microsoft.PowerShell
InstanceId             : c15cc80d-64f0-4096-a010-0211f0188aec
Id                     : 4
Name                   : Session4
Availability           : Available
ApplicationPrivateData : {PSVersionTable}
Runspace               : System.Management.Automation.RemoteRunspace

You can remove a remote Windows PowerShell session by piping the results of Get-PSSession to the Remove-PSSession cmdlet. This technique is shown here.

Get-PSSession -Name Session4 | Remove-PSSession

You can also remove a PS session directly by specifying the name to the Remove-PSSession cmdlet. This technique is shown here.

Remove-PSSession -Name session3

KG, that is all there is to using the built-in Windows PowerShell cmdlets to troubleshooting remoting. Join me tomorrow for the exciting conclusion to Microsoft MVP and honorary Scripting Guy Richard Siddaway’s workflow series.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Start Your PowerShell 3.0 Script in PowerShell 2.0

$
0
0

Summary: Learn how to start your Windows PowerShell 3.0 script in Windows PowerShell 2.0 mode.

Hey, Scripting Guy! Question I have a Windows PowerShell script I wrote several years ago. The script runs fine on a computer running Windows 7 with Windows PowerShell 2.0, but it does not work on my Windows 8 computer. I want to see if I can make the script work. What is the easiest thing to do?

Hey, Scripting Guy! Answer Start Windows PowerShell in version 2.0 mode. To do this, use the Run command and launch powershell -version 2. You can use the $psversionTable automatic variable to confirm you are now running in Windows PowerShell 2.0 (PowerShell will also be running in MTA apartment mode), as shown here.

PS C:\> $PSVersionTable

Name                           Value

----                           -----

CLRVersion                     2.0.50727.6400

BuildVersion                   6.1.7600.16385

PSVersion                      2.0

WSManStackVersion              2.0

PSCompatibleVersions           {1.0, 2.0}

SerializationVersion           1.1.0.1

PSRemotingProtocolVersion      2.1

PowerShell Workflows: A Practical Example

$
0
0

Summary: Honorary Scripting Guy and PowerShell MVP Richard Siddaway concludes his exciting series on Windows PowerShell workflows with a practical example.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the last article in the most excellent Richard Siddaway workflow 8-part series. 

Note The first article, PowerShell Workflows: The Basics, introduced the basic concepts of Windows PowerShell workflow. The second article, PowerShell Workflows: Restrictions, discussed the restrictions encountered with working with Windows PowerShell workflows. The third article was PowerShell Workflows: Nesting. The fourth article talked about PowerShell Workflows: Job Engine. The fifth article talked about PowerShell Workflows: Restarting the Computer. Next, was PowerShell Workflow: Using Parameters. And the last published article was PowerShell Workflows: Design Considerations. You should read these articles before getting into today’s article.

Once again, here is Honorary Scripting Guy and Windows PowerShell MVP Richard Siddaway.

Thanks, Ed, and thank you for the opportunity to put this series of articles together.

This is the last article of this series on Windows PowerShell workflows but by no means the last word on the subject. Workflows is a new concept with Windows PowerShell and, like all new concepts, it needs to be tested and experimented with to determine where it best fits into our administrator’s toolbox.

For this article, I thought I’d pull together the various strands of the series and present the development of a workflow. The scenario involves an organization that is expanding. It keeps creating new offices and needs to automate as much as possible of the IT administration associated with those new offices coming online. The list of activities that has to be performed includes:

  1. Create OU – parent OU for location
    1. Create Users OU as child
    2. Create Computers OU as child
  2. Create UPN
  3. Create accounts
    1. Create 35 computer accounts in correct OU
    2. Create 35 user accounts in correct OU
    3. Create home drives
  4. Create AD Site
    1. Create AD subnet & link to site
    2. Create AD Site Link
  5. Link GPOs
    1. Link GPO to Users OU
    2. Link GPO to Computers OU

The first and most important point is that this doesn’t need to be done as a workflow. I’d be surprised if there is any task that has to be performed as a workflow. It may be more complicated as a suite of scripts, but it will be possible. Having said that, we will use a workflow.

The big thing that workflows bring is parallelism, so you need to decide what can be done in parallel and what must be done sequentially. The other factor for the design is the order in which things happen, for instance, you need to create the OUs before you create the user accounts. 

Programmers have a concept called pseudo-code. It is used to map the logic of a piece of code under development. It’s not something that many scripters use, but for complicated situations like this, it can save a lot of rework.

If you turn the list of requirements into pseudo-code, you get something like this:

workflow pseudocode {

 parallel {

  create parent OU

  create UPN

  create AD site

 }

 parallel {

  sequence {

   create users OU

   read user data

   parallel {

    create user accounts

    create home drives

   }

   link GPO to users OU

  }

  sequence {

   create computers OU

   read computer data

   parallel {

    create computer accounts

   }

   link GPO to computers OU

  }

  sequence {

   create AD subnet

   create AD site link

  }

 }

}

Start with a parallel block to create the top OU, AD site, and UPN. These can be performed in parallel because there is no interaction between them. The bulk of the workflow occupies a parallel block that contains three sequence blocks—one for user accounts, one for computer accounts, and one for the remaining Active Directory topology. Within the sequence blocks, there is some scope for performing tasks—such as creating users—in parallel.

There are other possible ways to group these tasks—this is the way that it works for me. The workflow after some development and testing comes out like this:

workflow new-location {

param (

 [string]$locationname,

 [string]$subnet

)

 parallel {

  New-ADOrganizationalUnit -Path "DC=manticore,DC=org" -Name $locationname

  Set-ADObject -Identity "CN=Partitions,CN=Configuration,DC=manticore,DC=org" -Add @{upnsuffixes = "$locationname-manticore.org" }

  New-ADReplicationSite -Name $locationname

 }

 parallel {

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Users"

   ##

   ## read user data

   $password = ConvertTo-SecureString -AsPlainText -String "Pa55W0rd1!" -Force

   $users = Import-Csv  -Path  ./userdata.csv

   ##

   ## create users & home drives

   foreach -parallel ($user in $users){

     New-ADUser -AccountPassword $password  -Name ($user.Lname + " " + $user.Fname) -Path "OU=Users,OU=$locationname,DC=manticore,DC=org" -SamAccountName $user.id -UserPrincipalName "$user.id@$locationname-manticore.org"

     New-Item -Path "\\w12standard\home"  -Name $user.id -ItemType Directory

   }

   ##

   ## link GPO to users OU

   New-GPLink -Name "Management Configuration" -Target "OU=Users,OU=$locationname,DC=manticore,DC=org"

  }  

  ##

  ## create Computers OU, computers & link GPO

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Computers"

   $computers = 1..35

   foreach -parallel ($computer in $computers) {

    $cname = "$locationname-{0:000}"  -f $computer

    New-ADComputer -Path "OU=Computers,OU=$locationname,DC=manticore,DC=org" -Name $cname

   }

   New-GPLink -Name "W12Updates" -Target "OU=Computers,OU=$locationname,DC=manticore,DC=org"

  }

  sequence {

   New-ADReplicationSubnet -Name $subnet -Site $locationname

   New-ADReplicationSiteLink -Name "Site1-$locationname" -SitesIncluded "Site1", $locationname -InterSiteTransportProtocol IP -ReplicationFrequencyInMinutes 15

  }

 }

}

Two parameters are required—a location name and a subnet. The workflow can be used like this:

new-location -locationname Seattle -subnet "10.10.19.0/24"

The first step in the workflow is to create an OU for the location, an AD site, and a UPN.

parallel {

  New-ADOrganizationalUnit -Path "DC=manticore,DC=org" -Name $locationname

  Set-ADObject -Identity "CN=Partitions,CN=Configuration,DC=manticore,DC=org" -Add @{upnsuffixes = "$locationname-manticore.org" }

  New-ADReplicationSite -Name $locationname

 }

These tasks can be performed in parallel. There are no links or dependencies between the three tasks, so you don’t care about processing order. The New-ADReplicationSite cmdlet is new in Windows Server 2012. If you don’t have access to the cmdlet, you can modify this script:

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

## create the site

$site = new-object System.DirectoryServices.ActiveDirectory.ActiveDirectorySite($forcntxt, "MyNewSite2")

$site.Save()

You will need to run it as an InlineScript.

Moving into the bulk of the script, you have three sequence blocks that are processed in parallel. This where the real power of a workflow lies—you can create user accounts; computer accounts, and modify the Active Directory topology all in one pass. The first sequence block creates the user accounts:

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Users"

   ##

   ## read user data

   $password = ConvertTo-SecureString -AsPlainText -String "Pa55W0rd1!" -Force

   $users = Import-Csv  -Path  ./userdata.csv

   ##

   ## create users & home drives

   foreach -parallel ($user in $users){

     New-ADUser -AccountPassword $password  -Name ($user.Lname + " " + $user.Fname)

    -Path "OU=Users,OU=$locationname,DC=manticore,DC=org" -SamAccountName $user.id

    -UserPrincipalName "$user.id@$locationname-manticore.org"

     New-Item -Path "\\w12standard\home"  -Name $user.id -ItemType Directory

   }

   ##

   ## link GPO to users OU

   New-GPLink -Name "Management Configuration" -Target "OU=Users,OU=$locationname,DC=manticore,DC=org"

  }  

An OU is created for the user accounts. The locationname variable is referenced in a number of places. You are using the variable that is defined in a higher scope, and as you are working with parallel/sequence block, you just need the variable name.

A password is set. No, I wouldn’t do this in production—I’d pass it in as another parameter. Never code passwords in production scripts! The user information is read from a CSV file. In this case, the file contains first name; last name, and logon ID. Other data can be easily added and other parameters on the New-ADUser cmdlet utilized. If you have not used that cmdlet, I recommend looking at the Help file to see what is available. One oddity is the way I’ve had to create the name of the user account—pure string substitution didn’t work and you can’t use subexpressions in workflows.

I’ve hard-coded the file name, but the workflow could be modified to have the location name as part of the work flow. How about the idea of using the Windows PowerShell event engine to watch a particular folder. When a new file is dripped into it, the event triggers and runs the workflow! You can’t get much more automated than that!

The user account creation is performed using a foreach –parallel loop. These are freakily powerful. In this case, 35 user accounts created in parallel. The account creation and home drive creation steps are executed in sequence, but the 35 sets of that activity are performed in parallel. Trying to keep track of where the workflow is at a particular time is an interesting job. I think that with something like this it is only possible if you log the individual steps with timestamps.

The final step in this sequence is to link a GPO to the Users OU. 

Next up is creating the computer accounts:

sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Computers"

   $computers = 1..35

   foreach -parallel ($computer in $computers) {

    $cname = "$locationname-{0:000}"  -f $computer

    New-ADComputer -Path "OU=Computers,OU=$locationname,DC=manticore,DC=org" -Name $cname

   }

   New-GPLink -Name "W12Updates" -Target "OU=Computers,OU=$locationname,DC=manticore,DC=org"

  }

An OU is created for the computer accounts. I want to create 35 computers. I’ve hard-coded this for simplicity, but again, this could be a parameter. Alternatively, you could count the number of user accounts you are creating and create one computer account per user.

I’m using string formatting to create the computer name according to the organization’s naming convention. That is passed into New-ADComputer. A foreach –parallel loop is used again to maximize the parallelism of the workflow.

Last step is to link a GPO to the Computers OU.

The final sequence creates the last bits of topology:

sequence {

   New-ADReplicationSubnet -Name $subnet -Site $locationname

   New-ADReplicationSiteLink -Name "Site1-$locationname" -SitesIncluded "Site1", $locationname -InterSiteTransportProtocol IP -ReplicationFrequencyInMinutes 15

  }

These two cmdlets are Windows Server 2012−specific. If you don’t have access to them, you could try modifying these scripts. For the subnet:

## get current forest and set context

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

 

$site = "MyNewSite2"

$subnetlocation = "Building X"

$subnetname = "10.55.0.0/24"

 

## create subnet and link to the site

$subnet = New-Object System.DirectoryServices.ActiveDirectory.ActiveDirectorySubnet($forcntxt, $subnetname, $site)

$Subnet.Location = $subnetlocation

$subnet.Save()

 

For the site link:

## get current forest and set context

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

 

$link = New-Object -TypeName System.DirectoryServices.ActiveDirectory.ActiveDirectorySiteLink -ArgumentList $forcntxt, "MyNewSite3-MyNewSite4"

 

$site1 = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::FindByName($forcntxt, "MyNewSite3")

$site2 = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::FindByName($forcntxt, "MyNewSite4")

 

$link.Sites.Add($site1)

$link.Sites.Add($site2)

 

$link.Cost = 150

$link.ReplicationInterval = "01:00:00"   ## 1 hour   24x7

$link.Save()

 

$linkde = $link.GetDirectoryEntry()

$linkde.Description = "Links sites MyNewSite3 and MyNewSite4"

$linkde.SetInfo()

 

These would have to be used via an Inlinescript block. Remember you will need the “using” keyword to access variables from the higher parts of the workflow.

My hope is that this series of articles has shown you the power of workflows and how to get started using them. They are a very powerful resource that the Window PowerShell community is just starting to understand. This series is a step on that journey to understanding but is by no means the last. I would encourage you to experiment with workflows in your environment and share what you discover with the Windows PowerShell community. I can be contacted via my blog.

I suspect this won’t be my last word on workflows but until then—enjoy!

~Richard

Richard, thank you for a superb effort, a wonderful series, and an awesome amount of really great information on Windows PowerShell workflows.

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

Ed Wilson, Microsoft Scripting Guy


PowerTip: Use PowerShell 3.0 Cmdlet to Find PowerTips and Links

$
0
0

Summary: Use a Windows PowerShell 3.0 cmdlet to find PowerTip articles and the associated links.

Hey, Scripting Guy! Question How can I use a Windows PowerShell 3.0 cmdlet to find PowerTip articles and associated links to those articles?

Hey, Scripting Guy! Answer Use the Invoke-WebRequest cmdlet and specify the URI for the Hey, Scripting Guy! Blog. Use the links property and match the word “PowerTip” that starts the title for each PowerTip article. Finally, select the innertext and the href properties, as shown here.

$hsg = Invoke-WebRequest -Uri http://www.scriptingguys.com/blog

$hsg.Links | ? innertext -match '^Powertip' | select innertext, href

The PowerShell LoveOMatic Revisited for Valentine’s Day

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, revisits the LoveOMatic, a Windows PowerShell Script that sends random email messages at random times.

Microsoft Scripting Guy, Ed Wilson, is here. Several months ago, I wrote a Windows PowerShell script I called the LoveOMatic (See Weekend Scripter: Introducing the Scripting Guys PowerShell LoveOMatic). It was named after the Scripting Guys famous ‘omatic tools, such as the ScriptOMatic. The point of the script was to illustrate sending email by using the Windows PowerShell cmdlet Send-MailMessage. This is an extremely important technique from a monitoring perspective. I just thought I would have a bit of fun with it. It also illustrated using the Get-Random cmdlet, which is important from a scripting load balancing perspective. But the script could stand a bit of improvement. So, in honor of Valentine’s Day, I am introducing the LoveOMatic V2 script.

Note Even if you never intend to spam a loved one, the lesson today is a good one—how to improve a previously existing script. In fact, in past Scripting Games, I have engineered an event similar to this. All the time, we, as scripters, find a script that does basically what we need to do, and we revise the script. So this is, in effect, a real-life scenario.

Not everything needs to be in a function

It has become nearly a truism that when improving a script, the first thing that happens is to move “stuff” into a function.

Note Refer back to the original blog post to see the script under discussion.

But there are some scripts that do not necessarily gain much by moving things around into a function—this is one of them. Normally, I use a function to encapsulate code that will be reused or to separate out the logic of the script. This script is pretty much a straight-line script. I could move the For loop into a function, but that would not really gain me very much. Instead, I think I will keep the straight line execution in a script, and not move things to a function. There are three main improvements I will make.

The first is to add support for command-line parameters. Things that make sense are the number of emails to send, the minimum and the maximum time values between emails, and the messages themselves. The second improvement is to move the messages from hard-coded string literals stored in variables to a single text file. The third improvement uses the PSDefaultParameterValues feature of Windows PowerShell 3.0.

Adding command-line parameter support

When I run the script, I might like to be able to specify certain things from the command line, instead of opening up the script in the Windows PowerShell ISE and editing the text of the script. To do this, I use the Param statement. Because I also want the script to run like the other script ran, I assign default values for each parameter. If I want to shorten the min time, I can supply that from the command line, or if I specify no new value for the command-line parameter, then the script runs with default values. The parameter section is shown here.

Param(

$numberOfEmails = 6,

$minHour = 3600,

$maxHours = 10800,

$message = (Get-Content c:\fso\messages.txt))

Adding support for the text file

Because the original script used an array of messages, the only change I needed to do was to move the messages to a text file. Each message appears on its own individual line. When I use Get-Content to read the text of the file, it stores the results in the $message array. This is a major improvement in the script. The original code is shown here.

$message1 = "I am thinking of you."

$message2 = "You are the sunshine of my life."

$message3 = "You walk in beauty like the night."

$message4 = "I am glad I met you."

$message5 = "My life is richer with you a part of it."

The new code is shown here (it is part of the Param section):

$message = (Get-Content c:\fso\messages.txt))

Not only is the new code cleaner, but it also means the script is more flexible. I can change, for example, the entire contents of the file, or add new messages to the bottom of the file, or even change files. So, for example, if I want to send out Earth Day email messages, I simply create a new file named EarthDay.Txt and supply it at the message line, or from the command line when calling the script. Cool.

Creating PSDefaultParameterValues

Of the three big improvements I made to the original LoveOMatic, for me, the best improvement is the one where I create a new set of PSDefaultParameterValues. Keep in mind, I do not need to do this in my script—in fact, I normally would not. The big payoff comes when you do this IN YOUR Windows PowerShell PROFILE! Then, you do not even need to mess with the values. For example, useful defaults would be the From and the SMTPServer, because they would not change very often. If I am configuring something for a monitoring application, I might go ahead and specify the Subject and the To fields as well—as I have done here.

$PSDefaultParameterValues = @{

  "Send-MailMessage:From"="Ed@Nwtraders.msft"

  "Send-MailMessage:To"="ScriptingWife@Nwtraders.msft"

  "Send-MailMessage:Subject"="Happy Valentine’s Day"

  "Send-MailMessage:SMTPServer"="smtphost.nwtraders.msft"}

 

Note Keep in mind that I am here overwriting any previously stored default parameter values. If I need to ADD to the default values, I use the ADD method as shown here.

$PsDefaultParameterValues.add(“cmdlet:parameter,NewValue)

Advantage

By specifying the major parameters for my Send-MailMessage cmdlet, I have a very short command now to send email, as shown here.

Send-MailMessage -body $message[$rnd]

The LoveOMaticV2.ps1 script appears here.

LoveOMaticV2.ps1

# Requires -version 3.0

Param(

$numberOfEmails = 6,

$minHour = 3600,

$maxHours = 10800,

$message = (Get-Content c:\fso\messages.txt))

 

$PSDefaultParameterValues = @{

  "Send-MailMessage:From"="Ed@Nwtraders.msft"

  "Send-MailMessage:To"="ScriptingWife@Nwtraders.msft"

  "Send-MailMessage:Subject"="Happy Valentine's Day"

  "Send-MailMessage:SMTPServer"="smtphost.nwtraders.msft"}

 

For($i=0; $i -le $numberOfEmails; $i++)

{

  $rnd = Get-Random -Minimum 0 -Maximum 5

  Send-MailMessage -body $message[$rnd] 

  "message: $($message[$rnd]) sent at $((get-date).tostring())"

 Start-Sleep -Seconds (Get-Random -Minimum $minHour -Maximum $maxHours)

}

 

Join me tomorrow when I will begin a series of guest blog articles written by two extremely talented Microsoft PFEs—it is something you do NOT want to miss.

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

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Easily Create a Script that Creates PowerShell Aliases

$
0
0

Summary: Learn how to run a one-line Windows PowerShell command to create a Windows PowerShell script that automatically recreates all of your aliases.

Hey, Scripting Guy! Question I want to create a script that will create a standardized set of Windows PowerShell aliases for my environment. I need to do this in a single line. How can I easily do this?

Hey, Scripting Guy! Answer Use the Export-Alias cmdlet and specify the –As script parameter. Provide a path to store the script, as shown here.

Export-Alias -As script -Path c:\fso\aliases.ps1

Use PowerShell to Simplify Removing Old ADM Files from AD DS

$
0
0

Summary: Two Microsoft PFEs talk about a Windows PowerShell script to remove old ADM files from Active Directory Domain Services (AD DS) after converting to a central store.

Microsoft Scripting Guy, Ed Wilson, is here. Today is the first of a three-part series written by two Microsoft PFEs Mark Morowczynski and Tom Moser. First a little bit about Mark and Tom.

Mark Morowczynski (@markmorow) and Tom Moser (@milt0r) are Premier Field Engineers (PFEs) out of Chicago and Metro Detroit, respectively. They, with a group of contributing platform PFEs, write on the AskPFEPlat blog, covering a wide range of operating system, Active Directory, and other platform-related topics. Tom is a Red Wings fan. Mark is a Hawks fan. To date, no physical altercations have occurred due to this work conflict.

Take it away, Mark and Tom.

Scripting ADM removal

While working with our customers performing AD Risk Assessments (formerly known as ADRAP, now known as RaaS), we noticed that many customers were keeping their old ADM files around after implementing the Group Policy Central Store. For details on why we wrote the script, see our original posts here and here. The purpose of our guest post here on the Hey, Scripting Guy! Blogisn’t to cover why we wrote the script, but how the script works and some of our design ideas behind it.

When we started writing our ADM removal script, we knew that there were a few key concerns that customers had when cleaning up legacy ADMs. First, we knew that, while we said over and over again that nothing really bad would happen if somebody made a mistake, people wanted a way to quickly recover and restore the ADM templates. Second, we knew that people would want a way to see what would happen before actually doing anything. Finally, we found that people wanted a way to log the process. In addition to these requirements, we had to make the script work with DCs running both Windows Server 2003 and Windows Server 2008 R2, as well as be able to point to domains outside the user’s domain.

At this point, you may want to head over to the Script Center and download a copy of the script so you can follow along. Make sure you use the Windows PowerShell ISE or some other utility that shows line numbers.

The script takes a few parameters.  Briefly, the following table defines each parameter.

Switch/Parameter

Purpose

-BackupPath <Directory Path>

The path where we place the backed up ADM files.

-LogPath <File Path>

The path where we place the script log file in CSV format.

-WhatIf

Same as the Windows PowerShell –WhatIf switch. Shows what would have happened, but takes no action.

-Restore

Used with the LogPath and BackupPath switches to restore removed ADMs.

-DomainDN <Domain DN>

Used to specify the DomainDN if Active Directory Web Services (ADWS) is not available. Also used to specify an alternate domain.

-NoDateCheck

Skips the date-checking safety features. This prevents modified out-of-box ADMs from being removed.

-NoCentralStore

Skips checking the PolicyDefinitions folder on SYSVOL and checks the machine local PolicyDefinitions.

Finding the domain

The first thing we do in the script is try to find the domain. There are two ways it does this. First, as mentioned above, the user can specify the domain distinguished name (DN). We check for that in the line below.

if($domainDN -eq [string]::Empty)

If the domain DN is empty, we assume the user wants to use their own domain and that the user is going to use the Active Directory PowerShell module to do the work. This requires that the module is installed locally and there are DCs running either Windows Server 2008/R2 (Active Directory Web Services) or the Active Directory Management Gateway (for more, read Ashley’s blog here).

Inside of the if block we opened above, there’s a try/catch block.

try

            {

       #AD Module magic

import-module activedirectory

$DomainInfo = Get-ADDomain   

$DomainFQDN = $DomainInfo.dnsroot

$PDCEmulator = $DomainInfo.PDCEmulator

write-host -fore 'green' "Successfully imported AD Module"       

}

First, we attempt to import the Active Directory PowerShell module. The reason we wrap this in a try/catch block is to catch the failure to import the AD module. This could happen for two reasons. First, the AD module isn’t installed on the local machine. This is done via Server Manager on Windows Server 2008 and later, or via RSAT on Windows 7. The other reason it might fail is that the domain isn’t available or the module was unable to locate a DC running ADWS.

Assuming these two things are in place, we next want to use Get-ADDomainand store the result in a variable called $DomainInfo. By using this new object, we get the fully qualified domain name (FQDN) and we find the PDC emulator (PDCe). We look for the PDCe as it is the default administration point for Group Policy. We could bind to another DC, but for consistency, we choose the PDC. The AD module makes getting this information very easy, with no parsing or string manipulation required, as shown in the following image.

Image of command output

If exceptions are encountered during the try block, we’ll jump to the catchblock. In the catch block, we notify the user that the AD module encountered an error connecting to the domain. Instead of completely exiting out, the user is prompted to enter the domain DN (DC=contoso,DC=com, for example).

        catch

             {

             #The domain DN wasn't specified at start, but loading the AD module failed. Prompt user for the DN and proceed.

  write-host -fore 'red' "Encountered an error using the AD module. Verify that RSAT is installed with the AD `

            PowerShell module and that you have ADWS or ADMGW running on your DC."

        $continue = read-host "Would you like to enter the domain DN manually? Y or N: "

             if($continue.tolower() -eq "y")

             {

                             $domainDN = read-host "Please enter the domain DN in the proper format (ie, dc=corp,dc=microsoft,dc=com): "

            $confirm = Read-Host "Is this correct? Y or N: $($domainDN)"

                 if($confirm.tolower() -ne "y")

                             {

write-host "Exiting."

                        exit

                             }

             }

             else

             {

                                    return

             }

}

After telling the user that something failed, we ask if they’d like to continue. If the user opts to continue, they are prompted for the domain DN. We used the Read-Host­cmdlet to store the result of the user prompts and then use if statements to decide whether to continue. If the user doesn’t answer y to either question, the script exits.

Following the closing of the catch block, and then the closing of the previous if, we have another if statement. This statement checks to see if $domainDN is not empty. To make the comparison, we use the static property Empty in the string class.

if($domainDN -ne [string]::empty)

If it is empty, we skip the block and move in to function declaration. If $domainDN has a value, we then attempt to use old school Active Directory Service Interface, or ADSI, to bind to the directory. $domainDN will be set for one of a few reasons. Either the AD module import failed and the user was prompted for the DN (the user knew that they didn’t have the ability to use the AD module and specified the DN), or the user is running the script against an alternate domain. Next, we have this archaic-looking line.

$ad = [adsi]"LDAP://$($domainDN)"

Instead of using the (much more elegant) Active Directory PowerShell Module and ADWS, we rely on ADSI. First, we use the domain DN to create an object called $ad. To create this object, we use the ADSI LDAP provider, specifying the domain name as an LDAP string, LDAP://DC=corp,DC=contoso,DC=com. This returns an object that looks a little different than what we received from Get-ADDomain.

Image of command output

This requires a little more work to extract the information we need, namely PDCe and the domain FQDN. To obtain the PDCe role owner for the domain, we need to look at the fSMORoleOwnerattribute on the domain partition.

$PDCcn = ($ad.psbase.properties.fsmoroleowner).tostring().split(',')[1]

This returns an NTDS setting object that corresponds to the PDCe. It looks something like this.

Image of command output

This won’t work for our script, since we just want a DNS name. The end part converts this to a string (not needed, but safer), and uses the string string.Split() .NET Framework method to break the NTDS DN down into an array of strings. Since we already know the domain DN, all we need from this string is the common name (CN) of the DC, so we look at index 1 of the array (remember, it starts at zero). We store the result in $PDCcn and move on.

       $PDCEmulator = $PDCcn.split('=')[1]

       $DomainFQDN = $domainDN.tolower().replace("dc=","")

       $DomainFQDN = $DomainFQDN.replace(',','.')

            $PDCEmulator += ".$DomainFQDN"    

In the lines above, we again use the Split() method to split “CN=PDCName” to extract only the host name. We store this in $PDCEmulator. Following that, we take our DN-formatted domain name and use the String.Replace() method to clean up all of the domain component (DC=) parts of the DN. After we replace the commas in the DN with periods on the third line, we’re left with the domain DN. At the very end of the block, we append the PDC host name to the domain DN and voilá! We have an FQDN for the PDCe.

The section mentioned above, where the ADSI action happens, is also wrapped in a try/catch. Any exceptions thrown by the try block will result in an error message being output to the user and the script will exit.

catch

{

            write-host -fore 'red' "Error gathering domain information via ADSI. Please double check your DN formatting."

            return   

}

It’s pretty easy to see how the Active Directory PowerShell module makes life that much better. At this point in the script, we should have successfully queried the domain for all of the info we need.

Join us tomorrow for part 2.

~Tom and Mark

Thanks, Tom and Mark. I am looking forward to the next installment tomorrow.

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

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use a PowerShell Cmdlet to Obtain AD DS Replication Information

$
0
0

Summary: Use a Windows PowerShell cmdlet from the ActiveDirectory module to obtain information about replication connections.

Hey, Scripting Guy! Question I need to find information about the connections established for Active Directory replication. How can I use Windows PowerShell to do this?

Hey, Scripting Guy! Answer Use the Get-ADReplicationConnection cmdlet from the ActiveDirectory module, as shown here.

Get-ADReplicationConnection

Viewing all 3333 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>