Manage Windows Features with PowerShell: Add Feature or Role

To get back up to speed, I want to identify File-Service sub features that are not installed.

[chi-fp01]: PS C:\> import-module ServerManager
[chi-fp01]: PS C:\> Get-WindowsFeature file-services | select -expandproperty SubFeatures | get-WindowsFeature | where {-Not $_.Installed}

Display Name                                            Name
------------                                            ----
    [ ] Distributed File System                         FS-DFS
    [ ] File Server Resource Manager                    FS-Resource-Manager
    [ ] Services for Network File System                FS-NFS-Services
    [ ] Windows Search Service                          FS-Search-Service
    [ ] Windows Server 2003 File Services               FS-Win2003-Services
    [ ] BranchCache for network files                   FS-BranchCache


I’m betting you can figure out the name of the cmdlet to add a Windows feature.

[chi-fp01]: PS C:\> Add-WindowsFeature FS-DFS -WhatIf
What if: Checking if running in 'WhatIf' Mode.
What if: Performing operation "Add-WindowsFeature" on Target "[File Services] Distributed File System".
What if: Performing operation "Add-WindowsFeature" on Target "[File Services] DFS Replication".
What if: Performing operation "Add-WindowsFeature" on Target "[File Services] DFS Namespaces".
What if: This server may need to be restarted after the installation completes.

Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True    Maybe          Success   {}

[chi-fp01]: PS C:\>

Using –Whatif is a smart idea here as I can see that some additional features will also be installed. When I’m ready, I can re-run this command without –Whatif.

[chi-fp01]: PS C:\> Add-WindowsFeature FS-DFS

Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True    No             Success   {DFS Replication, DFS Namespaces}

In this particular case, no reboot was required. If it had been, I could reboot the server whenever I wanted with Restart-Computer, or wait until the next scheduled maintenance window. If you know in advance that installing a feature or role will require a reboot and you don’t mind rebooting immediately, use the –Restart parameter. If no reboot is required, then the parameter should be ignored.

Some features or roles include a number of sub-roles, as we’ve seen. You can either install a group of roles or you might decide to install a feature or role with all of its subroles.

[chi-fp01]: PS C:\> Add-WindowsFeature SNMP-Services -IncludeAllSubFeature -Restart -WhatIf
What if: Checking if running in 'WhatIf' Mode.
What if: Performing operation "Add-WindowsFeature" on Target "[SNMP Services] SNMP WMI Provider".
What if: Performing operation "Add-WindowsFeature" on Target "[SNMP Services] SNMP Service".
What if: This server may need to be restarted after the installation completes.

Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True    Maybe          Success   {}

[chi-fp01]: PS C:\>

I don’t need this feature but I wanted to show you how the parameters work. If you attempt to install a group of features that happen to have other requirements, you will be prompted to install them as well. In an automated solution, you will want to identify these situations ahead of time and install all the necessary features at once or in the right order.

The last management task is removing a feature. On my server, someone has mistakenly installed Telnet tools.

[chi-fp01]: PS C:\> get-windowsfeature telnet*

Display Name                                            Name
------------                                            ----
[X] Telnet Client                                       Telnet-Client
[X] Telnet Server                                       Telnet-Server

I want to delete them. The Remove-WindowsFeature cmdlet doesn’t support wildcards so I would have to type the names out, separated by commas. I’d rather have PowerShell do the work.

[chi-fp01]: PS C:\> Get-WindowsFeature Telnet* | Remove-WindowsFeature -whatif
What if: Checking if running in 'WhatIf' Mode.
What if: Performing operation "Remove-WindowsFeature" on Target "[Telnet Server] Telnet Server".
What if: Performing operation "Remove-WindowsFeature" on Target "[Telnet Client] Telnet Client".
What if: This server may need to be restarted after the removal completes.

Success Restart Needed Exit Code Feature Result
------- -------------- --------- --------------
True    Maybe          Success   {}

[chi-fp01]: PS C:\>

When I run the command without –Whatif, the features will be removed. There are any number of ways you might leverage this type of pipelined expression to remove non-standard features or to insure servers meet your build standards. And to repeat, we did all of this without any scripting. All we did was run some commands from a PowerShell prompt.

Manage Windows Features with PowerShell -part 1

Configuring and managing servers these days can be a time consuming process, unless you work more efficiently and smarter. For example, suppose you need to add a new server feature. Are you going to go to the server, open up server manager, scroll around to find where to add a feature or role and then click through the wizard? Or would you prefer to type a few commands and go to lunch?

With the increasing role of Server Core machines, GUIs aren’t even an option. Sure, you could use the Server Manager console from RSAT, but that still takes time to get going. What if you need to manage a feature on 10 servers?

Today I’ll show you what Windows features and roles were installed on a Windows Server 2008 R2 system using Windows PowerShell and the ServerManager module. In part two, I’ll add a feature or role to Windows features with PowerShell.

Manage Windows Server 2008 R2 with PowerShell

Your answer lies with Windows PowerShell – what else! I’m also talking about managing servers running Windows Server 2008 R2, ideally from a Windows 7 desktop. What makes this all possible is the ServerManager module. Unfortunately, this module only resides on server operating systems. I’ve never been able to find a way to load it on Windows 7, but that’s ok. Because the server is also running PowerShell 2.0 with remoting enabled, I can do everything from a remote PowerShell session.

If you need to automate the process across a number of servers, you can use the following as part of a script or scriptblock that you could execute with Invoke-Command. For now I’m going to deal with a single server.



PS C:\> enter-pssession CHI-FP01 -Credential globomantics\administrator
[chi-fp01]: PS C:\Users\administrator.GLOBOMANTICS\Documents> cd \
[chi-fp01]: PS C:\> dir

I now have a remote session using alternate credentials. The next step is to load the necessary module.

[chi-fp01]: PS C:\> import-module ServerManager

There are only 3 commands we have to work with, but that is plenty.

[chi-fp01]: PS C:\> get-command -Module ServerManager | Select Name


The cmdlet names should be self-explanatory. First, let’s see what features are already installed using Get-WindowsFeature.

[chi-fp01]: PS C:\> Get-WindowsFeature

You’ll see a nicely formatted listing of all features with an X indicating those that are installed. See Figure 1:

Get-WindowsFeature Output

Figure 1 Get-WindowsFeature Output

But don’t let the output fool you: we’re still dealing with objects.

[chi-fp01]: PS C:\> get-windowsfeature dns | select *

DisplayName          : DNS Server
Name                 : DNS
Installed            : True
FeatureType          : Role
Path                 : DNS Server
Depth                : 1
DependsOn            : {}
Parent               :
SubFeatures          : {}
SystemService        : {dns*}
Notification         : {}
BestPracticesModelId : Microsoft/Windows/DNSServer
AdditionalInfo       : {HelpLink, FeedbackLink, TechCenterLink, NumericId...}

Knowing the property names, I can modify the command to only find installed features.

[chi-fp01]: PS C:\> get-windowsfeature | Where {$_.Installed} | Sort FeatureType,Parent,Name | Select Name,Displayname,FeatureType,Parent

This one line command finds all installed features, sorted by a few properties in order and then displays a subset of object properties. Figure 2 shows the result:

Installed Features

Figure 2 Installed Features

Thinking of building an audit trail? It isn’t much more work to export results to a CSV, XML or plain text file.

Or you might want to drill down to a specific feature or role.
[chi-fp01]: PS C:\> $fs=get-Windowsfeature File-Service
[chi-fp01]: PS C:\> $fs | select *

DisplayName          : File Services
Name                 : File-Services
Installed            : True
FeatureType          : Role
Path                 : File Services
Depth                : 1
DependsOn            : {}
Parent               :
SubFeatures          : {FS-FileServer, FS-DFS, FS-Resource-Manager, FS-NFS-Services...}
SystemService        : {}
Notification         : {}
BestPracticesModelId :
AdditionalInfo       : {HelpLink, FeedbackLink, TechCenterLink, NumericId...}

Some of these properties are going to be nested objects. For example, SubFeatures will provide a list of additional features.

[chi-fp01]: PS C:\> $fs.SubFeatures

But these are simply names, which means I can pipe them to Get-WindowsFeature to see if any of them are installed.

[chi-fp01]: PS C:\> $fs.SubFeatures | Get-WindowsFeature

Display Name                                            Name
------------                                            ----
    [X] File Server                                     FS-FileServer
    [ ] Distributed File System                         FS-DFS
    [ ] File Server Resource Manager                    FS-Resource-Manager
    [ ] Services for Network File System                FS-NFS-Services
    [ ] Windows Search Service                          FS-Search-Service
    [ ] Windows Server 2003 File Services               FS-Win2003-Services
    [ ] BranchCache for network files                   FS-BranchCache

Now I can see what needs to be installed.

Configuring and managing servers is a task made easier by using Windows PowerShell. In the next article, we’ll look at adding and removing features.


TASK done by powershell

There are lots of server tasks in Windows Server 2008 that can be done much faster with Windows PowerShell than with a GUI. Some things can do are below :

1. Changing the local administrator password with PowerShell

Let’s assume you’re logged in as a domain administrator on a Windows 7 desktop that belongs to your domain. Now, let’s say you want to change the local admin password on a remote server in Chicago named CHI-WIN7-22. After an account password is used for some time, the chances of it getting exposed gets higher. That’s why you need to change your passwords from time to time.

The first thing to do to change the admin password in question is to create an ADSI object for the local administrator on that computer. That can be achieved by typing this in your PowerShell screen:


This will essentially retrieve the admin account on CHI-WIN7-22 and assign it to an ADSI object named $Admin. The WinNT monicker in that string is case-sensitive and is a common source of error, so take note of that. If you want to connect to another computer, just replaceCHI-WIN7-22 with the name of the computer you want to connect to.

Naturally, you’ll want to know first how long the password has been in use to determine whether or not the time has come to change it. You can obtain that information from $Adminby typing in:


That will display the time elapsed since the password of that account was last changed. However, since the resulting value is expressed in seconds, I normally divide it by 86,400, which is the number of seconds in a day:


The result will then be the same time elapsed but expressed in days, which I find more meaningful. If you notice, we used the Value property here. That’s because the PasswordAgeis actually stored as a collection, and so we need the value of that collection in order to return a number that we can perform a division operation on.

Finally, you can change the password by invoking the SetPassword method and then using the new password as the argument. For example, if you want the new password to beS3cre+WOrd, then type:


Note: After you hit enter, don’t expect any confirmation message because there won’t be any. Changes will take effect immediately. That’s because what we’re using here is a method, not a cmdlet. Which means, unlike with cmdlets, SetPassword has no support for a -whatif or a -confirm.

That’s all there is to it. Let me now show you the steps we’ve discussed here in theory on an actual PowerShell:

Change Password with PowerShell

2. Restarting or shutting down a server with PowerShell

Let’s now move on to the task of restarting or shutting down a server using PowerShell. Just like the first task, we’re still going to assume you’re logged in as a domain administrator on a Windows 7 machine that belongs to your domain.

For these tasks, we’ll be using a couple of WMI-based cmdlets, Restart-Computer and Stop-Computer. Although we won’t be showing them here, it’s worth mentioning that these cmdlets accept alternate credentials. Alternate credentials allow you to specify a user account other than the one you are already logged into so that you can perform actions that that (alternate) account has permissions for.

Another thing that’s nice about these cmdlets is that you’ll be able to make use of -whatifand -confirm. That means, if you want to do a restart or a shutdown, you’ll have a way of making  sure you’ll be doing it on the computer you intend to do it on. This can come in handy if you want to perform restarts or shutdowns on a number of computers. You can just pipe a list or group of computers to these cmdlets.

To restart a remote computer or computers, the basic syntax is:

Restart-Computer -ComputerName <string[ ]>,

wherein -ComputerName <string[ ]> is a string array that can be comprised of the name of a single computer or the names of multiple computers. Stop-Computer uses practically the same syntax. So for example, if you want to restart two computers named CHI-DC02 and CHI-FP01, the command would be:

Restart-Computer “CHI-DC02”, “CHI-FP01”

Here’s an actual PowerShell screenshot wherein we used the -whatif argument. You use a -whatif if you simply want to simulate what would happen if you would execute the command in question.


Restart Computer with PowerShell

That was pretty straightforward. Let’s now try a more sophisticated example. Let’s assume you have a list of computers in a file named servers.txt. You can use the Get-Content cmdlet to retrieve the contents of that text file, like this:

Get Content with PowerShell

So, if you have a bunch of computers that you want to restart on a regular basis, you can list down the names of those computers in a text file. Then each time you need to restart them, you simply use the Get-Content cmdlet. Here’s how we used Get-Content and Restart-Computer in a real-world scenario:

Restart Computers from Text File

First, we got the content from the text file using Get-Content. Then, because we wanted to prepare for the eventuality that some computers would be offline, we piped the list to awhere statement for testing. In the where statement, we ran test-connection, which is basically a ping on each computer.

The -quiet returns either true or false, while -count 2 means each computer will only be pinged twice. Those computers that were successfully pinged twice, were then passed along the pipeline.

Next, we used a foreach. Specifically, the objective was that: for each name that came out of the ping test, a green-colored message would be written saying that that computer was “Restarting”. The $_ stands for the current object in the pipeline. Next, the Restart-Computer cmdlet was called to restart each computer that could be pinged. We also used the-force parameter to kick off anyone logged on.

Finally, we used -whatif again to see what would happen without having to actually restart those computers.

3. Restarting a service with PowerShell

Restart-Service is the cmdlet used for restarting a service. Although this cmdlet does not have a built-in mechanism to connect to a remote computer, PowerShell Remoting can be enabled so that you can execute it locally via remoting on the remote computer. This can come in handy when you want to restart a service on a group of computers.

To restart a service locally, simply say: Restart-Service “service”, wherein “service” is the name of the service you want to restart. On the other hand, if you want to restart a service on one or more remote machines, then you can use the Invoke-Command cmdlet andPowerShell Remoting.  

In the PowerShell screenshot below, you see two instances wherein we executed theRestart-Service cmdlet to restart the service called wuauserv, which is the Windows Update service. In the first instance, Restart-Service is executed locally. But in the second instance, it is executed on a remote database server named CHI-DB01 with the help of the Invoke-Command.

Restart Service with PowerShell

By default, Restart-Service doesn’t write any objects in the pipeline unless you use -passthru. So the additional information you see at the bottom (Status, Name, etc.) is a result of using -passthru. If the service runs on multiple computers and you want to restart the service running there as well, just add more computer names in a comma-separated list.

Another way to do that same task is by using WMI. First, you create a WMI object:

Get WMIObject with PowerShell

gwmi is the alias for Get-WmiObject.

Let me show you first the methods of this object. To do that, we’ll pipe the object to Get-Member (alias is gm).

WMIObject Methods

If you notice, there is no method for restarting ther service. That means, we will have to stop the service using the StopService method and then start it again using the StartServicemethod.

Here’s how you stop the service using the object’s StopService method. The parenthesis indicates it’s a method. If you get a ReturnValue of 0, that means the service stopped successfully. In case you get another value, you can research what that value means by reading the MSDN documentation for the Win32 service class.

Stop Service Method with PowerShell

To fire the service up again, you use the StartService method.

Startservice Method with PowerShell

You can verify by executing the get-service command for that computer. Get-service allows you to connect to a remote computer, so you can simply get that service from the target computer to verify if it is in fact running there.

Get Service with PowerShell

4. Terminating a Process with Powershell

Another task that’s commonly done on a server is terminating a process. To terminate a process, you use the Stop-Process cmdlet. Again, this can be executed locally or, if you want to stop a process on a remote system, you can use Stop-Process along with PowerShell Remoting.

There are two ways of terminating a process using the Stop-Process cmdlet.

The first one is pretty straightforward. You just run the Stop-Process command and then pass to it either the name of the process or its corresponding ID. In the screenshot below, the name of the process being killed is ‘Calc’ (which is really just the Windows Calculator). Note that Calc is running locally in this example.

Stop Process with PowerShell

The second involves using the Get-Process cmdlet to get one or more processes and then piping them to Stop-Process to kill all those processes at the same time. In the screenshot below, the process being killed is Notepad. Note that kill is an alias of Stop-Process. Again, just like Calc in the previous example, Notepad is running locally.

Get Process Kill with PowerShell

Let’s now move on to an example where we have a process running remotely. First, let’s fire up a process to kill. So here, we’re starting notepad on a remote computer named chi-fp01.

Fire Up a Process with PowerShell

Next, let’s check whether the process is actually running. For this purpose, we use ps, which is an alias for Get-Process.

Get Process Alias with PowerShell

Ok. Now that we have a remote process to kill, let’s go ahead and kill it. Like what we did in our discussion on Restarting a Service, we’ll use Invoke-Command and PowerShell Remoting to run the Stop-Process expression on the remote server chi-fp01.

See how the Get-Process alias (ps), which is running in the script block, pipes the process to the Stop-Process alias (kill).

Killing a Remote Process using Invoke Command with PowerShell

5. Creating a Disk Utilization Report with PowerShell

As admins, we often need to keep track of how much disk space is being used on our servers. We can accomplish this using WMI and the Win32_LogicalDisk class, which will give us information such as the Device ID, the size of the drive, free space, and a few other bits of information.

Using WMI, we can query local or remote computers. We can also perform those queries on either a single or multiple machines. In addition, we can: export the data we query to a CSV file or a database; create a text-based or an HTML-based report; or simply display the output to the screen.

Here’s a sample command using WMI on a local computer.

Get-WmiObject win32_logicaldisk -filter “drivetype=3” | Out-File c:\Reports\Disks.txt

We use the GetWmiObject cmdlet to return information from the Win32_LogicalDisk class. Then we employ the -filter to return only information related to drivetype=3, which stands for fixed logical disks like the c: drive. That means, information regarding USB drives and network drives are not to be included.The returned information is then piped to a text file named Disks.txt.

Here’s a similar example done in an actual PowerShell where we could see an actual output. Note that we are using aliases to shorten the command. Also, in this example, we specified that the output would include the device ID, disk size, the free space, and the system name.

Get WMIObject with PowerShell

While there’s certainly nothing wrong with that output, it sure could use a couple of improvements. For example, you might want to display the size and free space in Gigabytes instead of bytes. We can actually get a more elegant output by adding a few extra steps. Let me show you how.

For this purpose, we’re going to create a function named Get-DiskUtil. Although the succeeding example is going to show you how to do things interactively in the shell, you can actually put this function in a script file, load it into your profile, or load it to your other scripts so that you can use it again later on.

Here’s the function I’m talking about:

Get Diskutil Function with PowerShell

Let’s dissect that function now.

The function is going to take a computer name as its parameter and it will default to the local computer name.

Function Parameter with PowerShell

Now we use the Process script blocks that this computer name property can be piped-in to the function. If it gets a piped-in value ($_), then it’s going to set the computer name variable to that piped-in value. Otherwise, it will take the computer name that gets piped-in as a parameter.

Process Script Block with PowerShell

Next up is the GetWmiObject expression.

Get WMIObject inside a function in PowerShell

The output of that expression is piped to the Select-Object cmdlet (represented by its alias,Select). We then make use of a hashtable to create a custom property calledComputername. This basically renames the SystemName of the current object ($_) toComputername. The DeviceID is passed along as is.

HashTables with PowerShell

We then deploy a couple more hashtables. The first one takes the Size property, divides it by 1GB, expresses the result into two decimal points, and renames the property to SizeGB. The second one takes the Freespace property and does practically the same thing to it.

Second set of Hashtables with PowerShell

Next, we create a new property called UsedGB, which doesn’t exist in WMI. It simply takes the difference between the Size and FreeSpace properties and divides the result by 1GB.

Creating a New Property with PowerShell

Finally, we also create another property called PerFree, which stands for “percent free”. This shows the free space as a fraction of total disk size expressed in percentage. And that completes the function.

Percent Free with PowerShell

Here’s the function in action wherein we passed to it the name of the computer, piped the output to Format-Table (or ft), and set the final output to auto-size using -auto.

Get Diskutil Function in PowerShell Action

While all this looks nice and pretty, there’s still a lot more that we can get from this function. So let’s say that on a weekly basis, you need to get a disk utilization report of all the servers in your environment. Here are a couple of different ways you can work with this data.


The first thing we’re going to do is to save the results of our expression to the variable $data. That’s so we don’t have to type in the command repeatedly. Next, we pipe the results to thewhere object, do the ping tests (pinging it twice when it can be pinged), and then pipe the computer name to our newly-created Get-DiskUtil function.

You’ll know that the command is done executing when you get the prompt back.

Using Get Diskutil Function in PowerShell

That would mean the data has already been stored in $data. You can then pipe the information in $data to sort by computername and then set it to auto-resize. You can also send that information to Out-Printer or Out-File.

Using the Output of the Get Diskutil Function in PowerShell

Similarly, if you want to load that information to a SQL database or an Excel spreadsheet, you can convert the data to a CSV file like this:

Export to CSV in PowerShell

Later on, if you import that CSV file, you will be able to obtain a snapshot of the disk utilization status of those disks right at the time the command is run.

Import CSV with PowerShell

Here’s a portion of that snapshot:

Snapshot of Disk Utilization

As a final example, let me show you how to create an HTML report that perhaps you will want to put on your Internet server to show disk utilization. So that, as an IT admin, you can go ahead and take a quick peek at your disk utilization status even while you’re outside the office.

So again, you start by taking $data and pipe it to Sort Computername. You then pipe the result to the ConvertTo-HTML cmdlet. You also give it a title and specify a CSS path. The CSS part is needed because ConverToHTML does not do any formatting. So if you want your report to look pretty you’ll need that CSS file. Finally, you need to send the output to a file.

Output to HTML

Now that your file’s ready, you can then look at the file by using the start command.

Show HTML Report with PowerShell

Here’s a sample of that HTML report.

Disk Utilization Report with PowerShell

Remember that the values on this report are up-to-date.

6. Getting 10 most recent event log errors

Every morning, you may have to go through your event logs to find the 10 most recent errors in the system event log on one or more computers. You can easily accomplish that task with PowerShell using the Get-EventLog cmdlet.

All you need to specify is the name of the log and the entry type. A typical command for this particular task would look like this:

Get 10 most recent event log errors


Here, the name of the log is ‘system’ and the entry type is ‘Error’. So PowerShell is going to fetch the 10 most recent error entries from the system log. This command is issued to a local computer, so we don’t have to specify a computer name.

Notice that the messages aren’t shown in their entirety. Let’s modify the command a bit so you can see those messages.

Modified Get-Eventlog

We simply piped the output of the previous command to ft, which is an alias for Format-Table, and then asked the table to display the following properties: Timewritten, Source,EventID, and Message. We also added the -wrap and -auto to make the output prettier. -wrap enables text wrapping, while -auto enables auto-sizing.

Here’s a portion of that command’s output:

Modified Get-EventLog


Let’s create yet another version of that command. This one sorts the properties by Sourceand then groups them. The final output is piped to more so that the display will pause one screen size at a time and not scroll all the way down.

Get Eventlog Sorted

Here’s a portion of the output:

Get Eventlog Sorted output

Notice how the items are grouped by source. The first set of information have EventLog as their source, while the second set have Microsoft-Windows-GroupPolicy. Notice also the — More — indicator at the end, which tells the user to press a key in order to view more information.

All those Get-EventLog commands we showed you were done on a local computer. Let’s now see how we can do this remotely.

For example, let’s say I want to see the 5 most recent errors on my domain controllers in my Chicago office. The computer names are chi-dc01 and chi-dc02. Let’s assume that I would like to sort and group the output by Machine Name. I would also like to show the following properties: Timewritten, Source, EventID, and Message. Again, I will add -wrap, -auto, andmore to get a more visually appealing look.

Get Eventlog on Remote Computers

Here’s a portion of the output.

Get Eventlog on Remote Comptuers Output

If you still recall what we did in task #5 of this article, you can pipe the output of this command to an HTML file and put the report on an Internet server. Anyway, there are lots of things you can do with this output. The important thing is that you now know how to get the needed information using the Get-EventLog cmdlet in PowerShell.


7. Resetting access control on a folder

There may be instances wherein the NTFS permissions of a folder are not set the way they need to be. If this happens, you will want to reset the access control on that folder. To accomplish that, you can use the Set-Acl (Set-ACL) cmdlet.

The easiest approach would be to use Get-Acl to retrieve the ACL from a good copy and then copy that ACL to the problematic folder. That will replace the existing ACL. Although it is also possible to create an ACL object from scratch, the first method (i.e., copying from a good copy of the ACL) is the recommended option, and that’s what we’re going to show you here.

Let’s say we have a file share called sales in a computer named CHI-FP01, and that file share has a ‘good’ copy of the ACL. To copy the ACL of sales and then store it in a variable named $acl, you execute this command:


Let’s have a look at the information inside that ACL:

Contents of ACL

See that Access property at the right? That is actually another object. To see the contents of that object, just use this command:

Access Object

Here are some of its contents:

Access Object Entries

As you can see, it is actually a collection of access control entries. If you only want to see identity references whose names match with “Sales”, enter the following command:

Selected Entries of Access Object

Now, if we use the same command to view the contents of the Access property belonging to a newly created file share named chicagosales, we get nothing. Note that we are using a shorthand notation here:

Viewing Access Object of Newly Created File Share

One possible reason why we aren’t getting any values is maybe the share was set up but the NTFS permissions were not properly provisioned.

Obviously, the solution to this problem would be to copy the ACL from our ‘good’ file share and apply it to our ‘bad’ file share. But first, we need to take the current NTFS permissions of the chicagosales file share and save them to an XML file. That way, if something goes wrong, we can simply roll-back by re-importing that XML file and use Set-acl to set those permissions back.

Save NTFS Permissions to XML

Once that’s taken cared of, you can then go ahead and apply a Set-Acl command on chicagosales using $acl, which is just the ACL  copied from our ‘good’ file share.


To verify whether we’ve succeeded, we can apply the same command we used earlier to display identity references whose names matched with “Sales”:

Get ACL to Verify

So now, the chicagosales NTFS permissions are the same as the sales permissions. That’s the easiest way to manage permissions and is one way to quickly address access control issues.

8. Getting a server’s uptime

You’re boss might want to be updated regularly regarding your server’s uptime. To get the information you need for this task, you use the WMI Win32_OperatingSystem class. This will return the value you need, and it works locally and remotely. The property you’ll want to look at is the LastBootUpTime. However, since it comes in a WMI format, you’ll want to convert it later to a more user-friendly date-time object.

Let’s start with an example using a local computer running Windows 7.

First, let’s save the results of a GetWmiObject expression to a variable named $wmi.

Get WMIObject

So now, $wmi will have some properties we can work with. The properties you will normally want to work with are the CSName (computer name) and LastBootUpTime.

WMI Object Properties

As mentioned earlier, the LastBootUpTime is a WMI timestamp, which is not very useful. So we need to convert that. Let’s save the converted value to a variable named $boot.

Convert to Date Time

We used the ConverToDateTime method, which is included on all the WMI objects that you get when you run GetWmiObject. The parameter you pass to that method is theLastBootUpTime property of the WMI object $wmi.

If you show the value of $boot, you’ll get something like this:

Converted Date Time Object

That is clearly a more useful piece of information than the original form of theLastBootUpTime. To find out how long that machine has been running you just subtract $boot from the current date/time, which can be obtained using Get-Date.

How long the machine has been running

The result is a TimeSpan object. You can get a more compact result by converting that object to a string using the ToString(). Practically every object in PowerShell is equipped with aToString() method.

Compact timespan object

The result above tells us that the machine has been running for 2 days, 5 hours, 46 minutes, and so on.

Let’s now put everything we learned here into a function called get-boot. Let me show you the function first in its entirety.

function get-boot

The function has a parameter that takes a computer name and defaults to the local computer name.

Get-Boot parameter

It also has a Process script block so computer names can be piped to it. Basically, if something is piped-in to it, the variable $computername will be set to the piped-in value. Otherwise, what will be used is whatever was passed as the parameter for computername.

Process script block

Included in the Process script block is the GetWmiObject expression specifying the remote computer name.


There are also a couple of hashtables. The CSName property is not so user-friendly, so it is replaced with a new property called Computername. There’s also a property called LastBootwhich contains the value of LastBootUpTime converted using the ConvertToDateTime()method. And then there’s one more property called Uptime, which is a TimeSpan object that shows how long the machine has been running.


If we run this locally (i.e., we don’t have to specify a computer name), the function defaults to the local computer name. Here’s the output:

Get-boot run locally

Just like what we did in item #2 of this article (“Restarting or shutting down a server”), you can save the names of your servers in a text file, process only those that can be pinged, and then pipe those names to the get-boot function.

get-boot from text file

That last screenshot shows you a list of remote computers and their corresponding last boot up time as well as their total uptime.

9. Getting service pack information

There are a couple of reasons why you would want to obtain service pack information of your servers. One reason is that you may be in the process of rolling out an upgrade and you need to find the computers that require a particular service pack. Another reason is that you may be doing an inventory or audit of your computers and part of the data that you would like to get is the level of service pack they are at.

Again, you can accomplish this using WMI and the Win32_Operating System class. There are several properties you may want to look at. This would include: the ServicePackMajorVersion, which is just an integer indicating the version pack such as 1, 2, or zero; the ServicePackMinorVersion, which is rarely seen but is there; and the CSDVersion, which will give you a string representation such as “Service Pack 1”.

If you have been reading this article from the beginning up to this point, you should know by now that you can use WMI to get a lot of information from your computers in Windows PowerShell. So again, we’ll employ Get-WmiObject and the Win32_operatingsystem class. The properties you’ll most likely be interested with are the CSName (computer name),Caption (the operating system), CSDversion, and the ServicePackMajorVersion.

Here’s a typical expression using all that.

Service Pack Information

As shown from the screenshot, this Windows 7 box is not running any service pack, so the ServicePackMajorVersion is zero, while the CSDVersion is blank.

Let’s now deploy our time-honored practice here of creating a function. Let’s create a function named Get-SP. As usual, we’ll take a parameter of the computer name, which will default to the local computer.

Get-SP function

Again, we use a Process script block. So if a computer name is piped-in, the variable $computername will be set to that piped-in object. The main part of this function is the Get-Wmiobject/Win32_operatingsystem class expression.

Just like before, we deploy a couple of hashtables. We take the CSName property and assign that to a property called ComputerName, which is much easier for a user to understand.  In the same manner, instead of using the Caption property, we use Operating System. And instead of using CSDVersion, we use SPName. Lastly, instead of usingServicePackMajorVersion, which is quite a mouthful, we simply use Version.

Of course, you can assign any property name you deem most suitable in your case.

Process script block for service pack information

Here’s the complete function along with a sample output when we run it locally:

Function for Service Pack Information

So now, we’re ready to grab our list of computers from our text file, only deal with those we can ping, and pipe-in each of those computer names to our newly-created get-sp function. Here’s the result:

Applying get-sp to a list of servers

You can see that CHI-DC02 is missing Service Pack 1, which has been released for Server 2008 R2. So I should, at some point, put in my project plan to update the Service Pack on that computer.

10. Deleting old files

The last common task we’re featuring here is the task of deleting old files. This is something you may need to do in cleaning up a file server or a user’s desktop. You may need to find files in those computers that are older than some date and delete them. To accomplish this, we’ll be using the Get-ChildItem cmdlet. It has an alias called dir, which is a term you’re more familiar with.

The best way to implement this task is to compare the LastWriteTime property on a file or folder object to some date-time threshold. If the LastWriteTime is beyond that date-time cutoff, then that’s a file you would want to delete. Just so you know, there’s also aLastAccessTime property but I rarely use that.

While LastWriteTime indicates when the file was last modified, LastAccessTime could be populated or changed by system utilities such as antivirus software or a defrag utility. Thus,LastAccessTime is not a good representation of the time when the file was last accessed by an actual user.

Once you find the files you’re looking for, you can pipe them to Remove-Item. BecauseRemove-Item supports -WhatIf and -Confirm, you can first run the command with those parameters, save the result to a text file, show it to your boss for approval, and then go ahead with the final deletion process.

Although it is possible to use a one-line pipeline expression in PowerShell to find particular files for deletion, I find this task best done using a script. That way, you can add some logging capabilities and support for whatif because if you’re deleting files, you really need to make sure you’re deleting the files you really intend to delete.

Here’s an example script file I wrote that does this job quite well. It’s available on the disk, so you can view the complete script from there. Look for the file called Remove-OldFiles.ps1. Here’s a portion of that script:

Script with Remove-Item

It’s actually an advanced script, which uses cmdlet binding that’s set toSupportShouldProcess=$True. That means, if you specify -whatif when you run the script, -whatif will be picked up by Remove-Item, which supports -whatif.

Notice the parameter named $Cutoff. That is the cutoff date. The default value is 180 days from the current date but you can specify another value and it will be treated as a date-time object.

The meat of the command is this:

Meat of the script

It will do a recursive directory listing of the specified path. More specifically, it will seek out all the files where the LastWriteTime is less than $cutoff.  Then it will save the files that meet that criteria to a variable called files using the common parameter -outvariable. Finally, any file that the where object picks up will be piped to Remove-Item, which will recursively go through and delete the files if it can.

Assuming files that meet the conditions for deletion are found, an xml file will then be created and stored in the current directory. The file will be given the file name Delete-yyyyMMddhhmm.xml, wherein ‘yyyyMMddhhmm’ is the current timestamp expressed in year-month-day-hour-minute. This will give you an audit trail for the files that are deleted.

Here’s the code snippet responsible for that:

list of deleted files in XML

So those are the main parts of the script. Let’s see it in action. First, let’s get a cutoff date that’s 270 days from the current date.

Getting the cutoff date

Next, we run the script. Now, let’s say I want to clean up the public directory of my file server in Chicago. The server’s name is chi-fp01 and I want to delete all files older than the specified cutoff date.

Here’s the command, along with the result notifications.

Running Script

Notice that you can see the path of the xml file that was exported, so you can check it out later if you want. For the meantime, let’s just see the first four files that were deleted. To do that, let’s just import the contents of the xml file to a variable named $data and then view the first four items in $data.

Deleted Files

If you look at the dates of those files, you’ll see how old they really were.


So there you have it! Windows Server 2008 provides a GUI that works great for several server tasks but as I hope these two articles have demonstrated, PowerShell can often be a quicker and more efficient approach to many Server 2008 tasks. I hope you learned a lot and I look forward to having you here again soon.

Connect to Windows 8 Remotely Using PowerShell

In this Ask the Admin, I’ll show you how to connect to Windows 8 remotely using PowerShell Remoting.

In my previous Ask the Admin post, I showed you how to configure PowerShell Remoting in Windows 8 using the command line, or Group Policy for PCs joined to an Active Directory (AD) domain. If you missed it, see How to Enable PowerShell Remoting in Windows 8. The steps in this article can also be used to remote to Windows Server 2012.

Establishing trust with the WinRM client

Before you can connect to a remote computer using PowerShell Remoting, that computer must be trusted by the WinRM client, i.e. the computer from which you are making the remote connection. In an Active Directory domain, or when PowerShell Remoting is configured to use HTTPS, Kerberos authentication and certificates respectively provide a means to determine if the remote computer is trusted, as long as the remote computer is specified by name and not IP address.

If both the remote computer and WinRM client are not members of the same domain, or not members of a trusted domain, then you will need to make an exception on the WinRM client computer to allow a connection to the ‘untrusted’ remote computer by making an entry for the remote computer in the TrustedHosts list on the WinRM client.

Adding a remote PC to a WinRM client’s TrustedHosts List

If you need to add a remote computer to the WinRM client’s TrustedHosts list, run the following PowerShell command from an elevated prompt, replacing mycomputername with the name of the remote computer you want to add to the list.

set-item wsman:\localhost\client\trustedhosts mycomputername -concatenate –force

To open a PowerShell command prompt with administrative privileges:

  • Log in to Windows 8.
  • Switch to the Start screen, type powershell and make sure that Windows PowerShell is selected in the search results.
  • Press CTRL+SHIFT+ENTER to start PowerShell with administrative privileges. If prompted, give consent or enter administrator credentials to continue.

Trusted Hosts can also be managed using Group Policy under Computer Configuration > Policies > Administrative Templates > Windows Components > Windows Remote Management (WinRM).

Making a PowerShell connection to a remote Windows 8 PC

Now we understand that one way or another the remote computer must be trusted by the WinRM client, we can make a connection.

  • Close the PowerShell prompt used in the previous steps. We don’t need administrative privileges on the local computer.
  • Switch to the Start screen, type powershell and make sure that Windows PowerShell is selected in the search results. Press ENTER to open a new PowerShell prompt.
  • In the PowerShell prompt type the command below, replacing username with the name of a user account that has administrative privileges on the remote computer, as required by the default PowerShell endpoints; and computername with the name of the remote computer.
Invoke-Command -ComputerName computername -ScriptBlock { get-culture } -credential username
  • In the security dialog, enter the password for the user account specified in the command and clickOK.

The above command runs the get-culture cmdlet on the remote computer. You can see the output in the figure below.

Use the Invoke-Command cmdlet to run commands on a remote PC


Use the Invoke-Command cmdlet to run commands on a remote PC. (Image: Russell Smith)

You can use the invoke-command cmdlet to run a command simultaneously on many computers:

Invoke-Command -ComputerName computer1, computer2, computer3 -ScriptBlock { get-culture } -credential username

Or to run a script as shown here:

Invoke-Command -ComputerName computer1, computer2, computer3 -filepath “c:\myscript.ps1” -credential username

Interactive sessions

If you want to type a series of commands, as if you were sitting at the local terminal, open a session on the remote computer.

  • Type enter-pssession computername in the PowerShell prompt and press ENTER, replacingcomputername with the name of the remote computer.
  • Enter an administrator username and password in the security dialog if prompted and click OK.
  • The prompt in the PowerShell window will now change to indicate that you are running commands on the remote computer.
  • To exit the session, type exit-pssession and press ENTER.


Start an interactive session on a remote PC

Start an interactive session on a remote PC. 

Persistent sessions

Finally, persistent sessions are useful if you need to share data between commands. To open a new persistent session type:

$session = new-pssession -computername computername

And then run some commands that pass data using a variable, $culture in this example:

invoke-command -session $session {$culture = get-culture} 
invoke-command -session $session {write-host $culture.Name}

The write-host cmdlet displays the name property of the culture of the remote computer in the PowerShell prompt, which in my case is en-GB.


Thanks For reading !!

Enable PowerShell Remoting in Windows 8

Enable PowerShell Remoting from the Command Line

If you want enable PowerShell Remoting on PCs that are not joined to a domain, or just on a handful of devices, then use the command line as shown below.

  1. Log in to Windows 8.
  2. Switch to the Start menu by pressing the WINDOWS key.
  3. On the Start screen, type powershell. Make sure that Windows PowerShell is selected in the search results and press CTRL+SHIFT+ENTER. Give consent or enter administrative credentials if prompted.
  4. In the PowerShell prompt, type enable-psremoting and press ENTER.
  5. You will then be prompted to confirm if you want to continue with the configuration. You can either confirm each step individually by typing [Y], or collectively by typing [A] and pressing ENTER.

Enable PowerShell Remoting from the command line

Enable PowerShell Remoting from the command line (Image: Russell Smith)

Alternatively, you can add the –force parameter to avoid having to confirm the configuration.

enable-psremoting –force

Once the command has completed, the WinRM listener will be ready to accept remote connections from any IP address.

Enable PowerShell Remoting using Group Policy

To enable PowerShell Remoting on PCs joined to an Active Directory domain, log on to a server or management PC that has the Remote Server Administration Tools (RSAT), using a domain account with permission to create new Group Policy Objects (GPOs) and link them to Organizational Units (OUs).

Configure a WinRM Listener

Let’s start by configuring a WinRM listener on HTTP:

  1. To start GPMC, open Server Manager using the icon on the desktop taskbar. Alternatively, you can use the icon on the Start screen.
  2. In Server Manager, select Group Policy Management from the Tools menu.
  3. In GPMC, expand your Active Directory (AD) forest and domain in the left pane.
  4. In the left pane of GPMC, right click Group Policy Objects and click New.
  5. In the New GPO dialog, give the new GPO a name and click OK.
  6. Expand Group Policy Objects in the left pane of GPMC, right click the GPO you just created and select Edit… from the menu.
  7. In the left pane of the Group Policy Management Editor window, expand Computer Configuration > Policies > Administrative Templates > Windows Components > Windows Remote Management (WinRM) and click WinRM Service.
  8. In the right pane, double click Allow remote server management through WinRM.
  9. In the Allow remote server management through WinRM dialog, check Enabled.
  10. In the IPv4 filter and IPv6 filter fields under Options, type * in both boxes to allow connections from any IP address, and then click OK.

Enable PowerShell Remoting using Group Policy

Enable PowerShell Remoting using Group Policy (Image: Russell Smith)

Set the WS-Management Service to Start Automatically

Now let’s configure the Windows Remote Management service to start automatically:

  1. In the left pane of the Group Policy Management Editor window, expand Computer Configuration > Policies > Windows Settings > Security Settings and click System Services.
  2. In the right pane, scroll down the list of services and double click Windows Remote Management (WS-Management).
  3. In the Windows Remote Management dialog, check Define this policy setting, and then check Automatic under Select service startup mode. Click OK.

Enable Windows Firewall Rules for WinRM

Finally we need to enable the default Windows Firewall rules for Windows Remote Management.

  1. In the left pane under Security Settings, expand Windows Firewall with Advanced Securityand click Inbound Rules.
  2. Right click Inbound Rules, and select New Rule from the menu.
  3. In the New Inbound Rule Wizard window, check Predefined and select Windows Remote Management from the menu. Click Next.
  4. Click Next on the Which rules would you like to create? screen to create the two default Windows Remote Management rules.

Note that you should consider deselecting the default rule for the Public firewall profile to ensure that Windows Remote Management isn’t exposed on unknown networks. Additionally, you might want to tighten the default Windows Remote Management firewall rule for Domainand Private networks.

  • On the final screen of the wizard, make sure that Allow the connection is selected and then clickFinish.
  • Close the Group Policy Management Editor window.