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

Weekend Scripter: Backup and Remove Old ADMs

$
0
0

Summary: Two Microsoft PFEs talk about a couple of functions to back up and remove old ADMs by using a Windows PowerShell function.

Microsoft Scripting Guy, Ed Wilson, is here. Welcome back to two PFEs Mark Morowczynski and Tom Moser for part two of a three-part series.

Please read yesterday’s blog to catch up if you missed reading it.

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.

Onward!

Building the paths

Next up, we need to take all of the information we put together about the domain and use it to build our paths. The very first thing we do, starting at line 111 in the script, is to create a string that contains the path to sysvol. This UNC path specifically points to the PDCe and not to the domain DFS namespace.

$sysvol = "\\$($PDCEmulator)\sysvol"

if($NoCentralStore.IsPresent -eq $true)

{

    $PolicyDefinitionPath = "C:\Windows\PolicyDefinitions"

}

else

{           

    $PolicyDefinitionPath = "$($sysvol)\$($DomainFQDN)\Policies\PolicyDefinitions"

}

 

$GPTPath = "$($sysvol)\$($domainFQDN)\Policies"

$backupTarget = $null

Next, we check to see if –NoCentralStore is set by looking for $NoCentralStore.IsPresent. If this property is true, we point to C:\Windows\PolicyDefinitions for the $PolicyDefinitions variable. The reason we check for this is that some customers expressed that they don’t want to use the GP Central Store. Instead, they use a terminal server for Group Policy management, and that server has local copies of all of the necessary ADMX templates. If $NoCentralStore.IsPresent returns anything but true, including null, we use the $sysvolvariable to build out the path to the domain’s GP Central Store. Following that, we build the path to the domain Policies folder, and store it in $GPTPath. This path contains all of the Group Policy templates. Finally, we set $backupPath to $null as a placeholder.

Finally, before getting to function declaration, we create an object called $ADMDateStamps by using the ConvertFrom-CSVcmdlet. We pass in headers, for naming the properties, as well as the names and dates associated with all out-of-box ADMs for operating systems prior to Windows Server 2008.

(This is just a small part.)

$ADMDateStamps = ConvertFrom-Csv `

                "ADM,DateStamp

                conf.adm,2/22/2003

                inetres.adm,2/18/2005

                system.adm,2/18/2005

                wmplayer.adm,2/18/2005

                             …

 The object this cmdlet creates looks like this.

Image of command output

We’ll come back to that later on in our functions.

Function declarations

Now, we’ll discuss where all of the work takes place. The script contains six functions. For the sake of brevity, they’re briefly described in the following table.

Function

Purpose

GetADMTemplates

Performs a recursive Get-Childitem for *.adm in the $GPTPath.

GetADMXTemplateHashTable

Gets a list of *.admx from $PolicyDefinitions and stores information about them in a hash table.

BackupAndRemoveADM

This function does the real work. It backs up, removes, and logs all actions.

IsValidPolicyDefinitonPath

A simple function to verify that the Policy Definition Path is valid.

CheckADMDate

A simple function to check the out-of-box ADM dates against the dates found on sysvol. Returns $false if the date isn’t found.

RestoreADMFiles

When used with the $logpath parameter, restores all ADMs to the original location.

 Some of these functions are just a few lines long. For portability and reusability, it made more sense to functionalize them (is that a word?). Since some of these are pretty brief and self-explanatory, we won’t cover them all.

Function: GetADMTemplates

As mentioned above, this is a simple function.

function GetADMTemplates

{

    param([string]$GPTPath)

    $ADMTemplates = get-childitem $GPTPath *.adm -recurse | where { $_.extension.length -eq 4 }   

    return $ADMTemplates

}

The function takes in a string parameter called $GPTPath. Based on the paths we constructed earlier in the script, this should be the UNC path to the sysvol folder on the PDCe for the domain. Line 4 does a simple Get-Childitemwith the –Recurseswitch. We also filter for *.adm. This will return all files in sysvol that end in .adm* (including ADMX and ADML). Since we want only *.ADM, we pipe the results of the Get-Childitemto a where-objectand specifically filter for any files with an extension of 4 characters or fewer, including the period. The result is returned to the script body by using a return statement.

Download the function from the Script Repository.

Function: GetADMXTemplateHashTable

Hash tables in Windows PowerShell are a great way to use a dictionary-type search when checking for the existence of an object. The hash table allows us to create a key/value pair that we can later use to quickly check to see if a specific ADMX name was found anywhere in sysvol.

Function GetADMXTemplateHashTable

{

    param([string]$PolicyDefinitionPath)

       

    $ADMXTemplates = @{}

    $ADMXFiles = get-childitem $PolicyDefinitionPath *.admx

   

    if($ADMXFiles -eq $null)

    {

        write-host -fore 'yellow' "No ADMX templates found in the central store."

        return

    }

    if($ADMXFiles.count -eq $null)

    {

        $ADMXTemplates.Add($ADMXFiles.Name, $ADMXFiles.Fullname)

    }

    elseif ($ADMXFiles.count -gt 1)

    {

        $ADMXFiles | foreach { $ADMXTemplates.Add($_.Name, $_.Fullname) }  

    }

   

    return $ADMXTemplates

}

Above, you can see that we only require a single parameter for this script— $PolicyDefinitionPath. Next, we create an empty hash table using $ADMXTemplates= @{}.Next, we use Get-Childitem, recursively searching for *.admx, storing the result in a variable called $ADMXFiles.

We need to evaluate the result of that search to find out if it found one object or many. We do this by looking for the count property on the returned object. A single object doesn’t have this property, but an array of objects will have the count property. The second if statement checks for the count property.

Since a property that doesn’t exist on an object is equal to null, we just check to see if count is equal to $null. If it is, we got only one file back from our Get-Childitem. If it isn’t null, we know that count must be greater than one, so we check for that. In either scenario, we then iterate through every file (or just the one) and build a set of key/value pairs in our hash table. The file nameis stored as key and the full file path is stored as the value. More on that later. At the end, we return the hash table back to the caller.

Download the above function from the Script Repository.

Function: BackupAndRemoveADM

As noted in the script comments, #This function does all of the work.

First, the function takes in three parameters: $ADMTemplatePath, $BackupPath, and $BackupTarget. We’ve already defined the purpose of those first two parameters. (Hint: They’re script parameters.) The last one is dynamically constructed in the script body and we’ll talk about it later.

While this BackupAndRemoveADM function does all of the work, it’s a relatively simple function.

First, we use Test-Pathto see if the path specified in $backupPath actually exists. If not, we use the New-Item cmdlet and make sure to use the –ItemType parameter to specify that this should be a directory and not a file, function, alias, Active Directory object, or anything else we can create with the wonderfully generic New-Item cmdlet.

    if((Test-path $backupPath) -eq $false)

    {

        New-Item -ItemType directory $backupPath

    }

Next, we need to create the GUID-based backup path. This is where $backupTarget comes in to play. Down in the main body of the script, we have this:

$backupTarget = $BackupPath + "\" + $ADMTemplate.FullName.split('\')[6]

That line is located in a Foreach loop that iterates through all of the ADMs found on sysvol. The line performs a String.Split() on the full name of the ADMTemplate using \ as a delimiter. We pull out the 6th element and append to the user-specified backup path. An example of that process would look like this.

\

\

PDC.Corp.Contoso.Com\

Sysvol\

Corp.contoso.com\

Policies\

{GUID}\

ADM\

System.ADM

0

1

2

3

4

5

6

7

8

 In the path above, the characters preceding each slash becomes an element in the array. We wanted the 6th element, as this is the GUID for the GPO. We append that to the specified backup path and come up with the following.

C:\MyADMBackup\{2F1B9A33-F347-4010-9492-157C08B71F54}

Back in the function, we check to see if that path exists. If not, the file is created. What this ends up doing is populating the backup folder with a subfolder for every GUID found in sysvol.

Next, we actually copy the ADM located at $ADMTemplatePath to the path set in $backupTarget. Since we want to be extra sure that the ADM was successfully backed up, we make use of the Windows PowerShell $? variable. Consulting Get-Help about_automatic_variables tells us exactly what the variable does.

Image of command output

We can use $? to ensure that our backup, or other commands, was actually successful before we go and remove the file.

copy-item $ADMTemplatePath $backupTarget 

if($? -eq $true)

            {

       remove-item $ADMTemplatePath

            write-host -fore 'yellow' "Removed $($ADMTemplatePath)"       

       }  

If the if statement evaluates to true, we remove the file, and in yellow font, we write to the console that it was removed.

You can download the above function at the Script Repository.

Like we said, simple!

~Tom and Mark

Thanks again, Tom and Mark, for sharing this knowledge and your time. Be sure to join us tomorrow for the conclusion.

 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: Find the Start Mode and Startup Account for Services

$
0
0

Summary: Use Windows PowerShell to find the start mode and startup account used by services.

Hey, Scripting Guy! Question How can I use Windows PowerShell to find the startup account and the start mode of services on my system?

Hey, Scripting Guy! Answer You need to use WMI and query the Win32_Service class.

With Windows PowerShell 3.0, use the Get-CimInstance cmdlet, as shown here.

PS C:\> Get-CimInstance -ClassName win32_service -Property startmode, startname | sel

ect startname, startmode, name

With Windows PowerShell 1.0 and 2.0, use the Get-WmiObject cmdlet to query the Win32_Service WMI class, as shown here.

PS C:\> Get-WmiObject -Class win32_service -Property startmode, startname | select st

artname, startmode, name

Weekend Scripter: Use PowerShell for ADM Cleanup—The Exciting Conclusion

$
0
0

Summary: Two Microsoft PFEs conclude their three-part series on cleaning up old ADM files from AD DS.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the conclusion of a three-part series written by PFEs Mark Morowczynski and Tom Moser.

Be sure to read the blog for Friday and Saturday first if you have not already done so.

Mark Morowczynski (@markmorow) and Tom Moser (@milt0r) are Premier Field Engineers (PFE) 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 …

Well, it is now time to take a look at the CheckADMDate function.

Function: CheckADMDate

On the function home stretch here, we’ve got CheckADMDate. This function takes in a few parameters: $ADMDateStamps, $ADMName, and $Date, the latter two being typed as strings.

You may recall that near the top of the script, we defined $ADMDateStamps by using Import-CSV on a comma-separated list of names and dates. This function simply iterates through that array of objects we created and uses a Where-Object cmdlet to check to see if the ADM file name and date match the name and date of the specified ADM.

$match = $ADMDateStamps | Where { $_.ADM -eq $ADMName -and $_.DateStamp -eq $date }

 

if($match -eq $null)

{

    return $false

}

return $true 

If no match is found, then $match will not be created, meaning it will be equal to null. The function returns false. This indicates that the ADM template name and date don’t match any of the out-of-box ADMs and dates. Otherwise, it returns true.

The purpose of this check is to prevent people from deleting out-of-box ADMs that have been modified at some point along the way. It also helps to verify that somebody didn’t go and create an ADM called “system.adm” that doesn’t actually match the new out-of-box ADMX templates.

You can download the above function from the Script Repository.

Function: RestoreADMFiles

Finally, RestoreADMFilesis the function that seemed most important to people as we talked to our various Premier customers during ADRAPs, GPO Health Checks, and other PFE offerings. This function takes in a single parameter, $logPath. This variable contains the path to the CSV log file that was created when removing the ADM templates. Everything else needed to restore is in the log.

We use Import-CSV to read the log and covert it to an object array. The log contains five columns: TemplateName, FullPath, ADMXInCentralStore, BackupLocation, and NoMatch. When we get to the body of the script, we’ll go over those columns. The restore function utilizes BackupLocation and FullPath.

After using Import-CSV to read in the CSV log and create an object array based on the contents, we iterate through the collection, item by item, and simply copy the file from the backup path to the original ADM location, assuming the backup location isn’t empty, for some reason. 

foreach ($entry in $logData)

{

if($entry.BackupLocation -ne "")

       {

           copy-item $entry.BackupLocation -destination $entry.FullPath   

           if($?)

           {

             "Restored $($entry.FullPath)"

           }

       }

}   

 Like before, we use $? to verify that the restore was a success, then notify the user.

The script body

We’ve made it.

The first thing that happens in the script body is to check to see if the user has specified the –Restoreswitch. If they have, we call RestoreADMFiles, passing in the log path. Once the function is complete, the script exits.

Afterward, assuming the script has progressed that far, we call IsValidPolicyDefinitionPath, passing in the $PolicyDefinitionPath as the parameter. If the function return false, the user receives output saying that the path is invalid (whether it be the GP central store or local path) and the script exits. If the –NoCentralStore switch is not used when running the script, this function effectively verifies that the central store has actually been created.

Next, we use the Split-Path cmdlet on the $logPath variable that the user specified and store the result in a variable called $logDirectory. Split-Pathcan be given a full file name and will split the directory and file, returning just the directory. This is much quicker than attempting to use the –split operator or .NET Framework String.Split() method to separate a directory and a file.

$logDirectory = Split-Path $logpath

Once we’ve got the directory, we verify that it exists and create it if it doesn’t.

At this point, we’ve successfully bound to Active Directory, created all of the necessary paths and files, checked all of the inputs, and it’s time to get to business. So, we write the log file header:

set-content $logpath "TemplateName,FullPath,ADMXInCentralStore,BackupLocation,NoMatch"

Then, build the hash table containing information about all of the ADMX templates stored in the PolicyDefinitions folder:

$ADMXTemplates = GetADMXTemplateHashTable($PolicyDefinitionPath)

Run GetADMTemplatesto do the recursive Get-Childitem on sysvol:

$ADMTemplates = GetADMTemplates($GPTPath)

Check to see if we actually found any and return a message if we didn’t:

if($ADMTemplates -eq $null)

{

    write-host -fore 'yellow' "No ADM Templates found."

    return

}

Then, we begin to iterate through all of the discovered ADM templates in a big foreach loop:

foreach ($ADMTemplate in $ADMTemplates)

The first thing we do in the loop is split the ADM name from the .ADM file extension, and then store the result in $TemplateName.

$TemplateName = $ADMTemplate.name.split('.')[0]

if($TemplateName -eq "WUAU") { $TemplateName = "WindowsUpdate" }

if($TemplateName -eq "wmplayer") { $TemplateName = "WindowsMediaPlayer" }

if($TemplateName -eq "system") { $TemplateName = "windowsfirewall" }

In the last three lines in the snippet above, we check $TemplateNameand reset the value if it’s equal to WUAU, wmplayer, or system. This is because the new ADMX templates have different names or they’re a bit less monolithic.

After, we enter a boolean switch statement:

switch($ADMXTemplates.Contains("$($TemplateName).admx"))

For the condition, we simply append .admx to the $TemplateName variable and go back to the hash table we created earlier. The beautiful thing about the hash table is its quick, dictionary-like lookups. If I want to know that the WindowsUpdate.admx file exists in the central store, I only need to run:

$ADMXTemplates.Contains("WindowsUpdate.admx”)

This will quickly do the lookup and return a true/false value. No need to repeatedly loop through the result of a Get-Childitem that has been stored in a variable or some other kind of object.

Assuming this returns true, the script falls to the $truecase. 

$true that.

Inside the $true case, the script evaluates the $NoDateCheck variable. If it’s not equal to true, meaning the user did not set the switch when running the script, the script runs the CheckADMDate function for each ADM that was found.

if((CheckADMDate -ADMName $ADMTemplate.Name -Date $ADMTemplate.LastWriteTime.toshortdatestring() -ADMDateStamps $ADMDateStamps) -eq $false)

       {

"$(($ADMTemplate).FullName) does not match any timestamps from Windows Server 2003 or Windows XP or any service packs. Verify that it has not been modified." #wrapped from above

              continue

       }

CheckADMDateis called, and the current ADM name, ADM last modified date, and $ADMDateStamps array (remember Import-CSV from the beginning?) are passed in to the function. If we find the name and date in the list, the function returns true and the script continues on. If it isn’t found, we notify the user that it was not found, and then use the continue keyword to break out of the current iteration in the for loop.

This means that processing for the current ADM template stops at line 318 in the script, and we loop back around to the next one. This check prevents the ADM from being removed if there’s a date mismatch.

$whatIf I don’t say –WhatIf? 

if($whatIf -eq $false)

{           

BackupAndRemoveADM -ADMTemplatePath $ADMTemplate.FullName -BackupPath $BackupPath -BackupTarget $backupTarget                       

}

These four lines are the payload. If the user didn’t specify –WhatIf on the command line (because when I test, I test in prod), this expression will evaluate to $false. The function BackupAndRemoveADMis called, passing in the ADM’s full path, the backup location, and the explicit backup target (that path that contains the GUID). The script makes a copy, deletes the original, and continues on to:

add-content $logpath "$(($ADMTemplate).Name),$(($ADMTemplate).FullName),True,$($backupTarget)\$(($ADMTemplate).Name)"                     

Where it logs ADM name, full path, true (meaning the ADMX was in the central store), and backup location.

$false…that?

The last few lines of our script contain the $falsecase from our switch statement. If the switch statement evaluated to false, that means we didn’t find a matching ADMX template for our ADM template. The ADM file is not removed, and we write a message to console indicating that. Finally, we write to the log file:

add-content $logpath "$(($ADMTemplate).Name),$(($ADMTemplate).FullName),False,,True" 

This appends another line to the log file, containing just the ADM name, the full path, then the values false, null, and true. The False value indicates that the script was unable to locate the matching ADMX in the central store. The backup location is left null because the script didn’t actually remove anything, and the last value, NoMatch, is set to True since no match was found in the central store.

Wrap up

At this point, we hope you’ve got a good understanding of how this script works and the techniques we used to allow admins to safely remove and restore their ADM templates. We hope that you, or your Active Directory administrators, will feel comfortable removing the space consuming ADM templates with the assistance of our script. Keep an eye on the Script Center for future updates. Writing this post has given us a few more ideas for the future.

~Tom and Mark

Thank you, Tom and Mark.

That’s it for this week. Join us tomorrow for some more Windows PowerShell fun.

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: Find Your Personal PowerShell Aliases

$
0
0

Summary: Learn how to find your personally created Windows PowerShell aliases.

Hey, Scripting Guy! Question I need a way to identify personally created Windows PowerShell aliases. How can I easily do this?

Hey, Scripting Guy! Answer When creating a custom alias, specify the Description property. You can then search for aliases that use that description, as shown here.

New-Alias -Name dog -Value get-content -Description 'mred alias'

Get-Alias | ? description -match 'mred'

Use PowerShell and a Web Service to Find Holidays

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, shows how to use Windows PowerShell to find the date for a specific holiday by using a web service.

Hey, Scripting Guy! Question Hey, Scripting Guy!  I have a problem. I guess you hear that a lot. Anyway, my boss has given me what I think is an impossible task—to find the holidays we have coming up this year. Not just a list of the standard holidays, but the actual days they will be celebrated. I know there is a holiday thing I can turn on in Microsoft Outlook, and I thought about trying to write a script that would find holidays marked in my Outlook Calendar, but I suspect that my boss has other ideas. You see, he never tells me what is on his mind. It is always just a series of seemingly random requests. I really wish he would clue me in on what goes on in his fuzzy-headed brain, but that is a different story—so I have to try and anticipate him and that is not easy. Anyway, I suspect, eventually he wants a script that will be able to do something with holiday dates—got any clues, ideas, or suggestions, oh, scripting wizard?

—GG

Hey, Scripting Guy! Answer Hello GG,

Microsoft Scripting Guy, Ed Wilson, is here. This morning seems cold. A quick look outside reveals frost on the ground. In Charlotte, North Carolina, in the southern portion of the United States, having frost on the ground is not a normal event. In fact, I would not be surprised if they called off school. But as the old song goes, the weather outside is frightful, but the fire inside is delightful. Yes, I turned on the fire when I came down and saw the glittering grass in the early morning sunlight. I also decided today would be a good coffee day. The Scripting Wife had a nice bag of Kona coffee shipped from the island of Kauai, Hawaii, for me, and today seems like a nice day to open it. When I make coffee, I grind fresh beans, use fresh spring water, and I employ my French press. I usually use turbinado raw sugar, but today I am using crushed rock sugar I brought back from Munich—it is actually a nice addition. For me, I want to taste the coffee, not cheap flavoring. Nor do I want to drink a glass of warm milk with a shot of coffee in it.

Use a web service to obtain holiday dates

GG, there may be a completely international web service that returns holiday information, but I was unable to find one doing a few Bing searches. I was able to find a web service that contains holiday information for a few countries. I found the HolidayService2 web service. The service defines six methods. Opening the URL in Internet Explorer shows the six methods.

Image of HolidayService2 web service

To use a method from a web service, I use the New-WebServiceProxy cmdlet, and I store the returned object in a variable. When using the New-WebServiceProxy cmdlet, the most important parameter is the URI for the web service. For this example, the classand the holidayparameters do not matter. To make things more readable, I always store the URI for the web service in a separate variable. I end up with two lines of code, as shown here.

$URI = "http://www.holidaywebservice.com/HolidayService_v2/HolidayService2.asmx"

$proxy = New-WebServiceProxy -Uri $URI -Class holiday -Namespace webservice

Calling a web service method

All I need to do now is figure out how to use the web service. This particular web service is really nice because it is based on the .NET Framework, and it returns .NET objects—I will get to that in a minute. First, I need to know what countries or regions are supported by the web service. To do this, I call the GetCountriesAvailable method.

Note  Keep in mind this web service is case-sensitive. Therefore, UnitedStates must be cased as shown (I cannot do everything in lowercase as I often do), as it is required for the other countries or regions and holidays.

To call GetCountriesAvailable, I begin typing and use the tab expansion to complete the method name. This command is shown here.

$proxy.GetCountriesAvailable()

The command and associated output appear in the image that follows.

Image of command output

OK, so I know what countries or regions the service supports. Now I need to know what holidays the service supports. To do this, I call the GetHolidaysAvailable method, as shown here.

$proxy.GetHolidaysAvailable("UnitedStates")

The neat thing about the service returning objects is that I can pipe the returned objects to other Windows PowerShell cmdlets. For example, looking through a random listing of holidays may not be too illuminating, but if I sort them alphabetically, it makes things easier to see. This command is shown here.

$proxy.GetHolidaysAvailable("UnitedStates") | sort code

The command and associated output appear here.

Image of command output

Hey, want to know the difference between holidays celebrated in Northern Ireland and the holidays celebrated in the Republic of Ireland? I call the GetHolidaysAvailable method twice (once for each region), store the results in variables, and pass the resultant objects to the Compare-Object cmdlet. Piece of cake. The three commands are shown here.

$in = $proxy.GetHolidaysAvailable("IrelandNorthern") | sort code

$ir = $proxy.GetHolidaysAvailable("IrelandRepublicOf") | sort code

Compare-Object -Property code -ReferenceObject $in -DifferenceObject $ir

The commands and the associated output are shown here.

Image of command output

Get the holiday date

Now, I can figure it out. It might take a bit of time. A few of my attempts are shown here.

Image of command output

Rather than cluttering up my Windows PowerShell console with red, how about if I go back to the HolidayService2 web page and look at the parameters for the GetHolidayDate method. In fact, there is even a little form I can use to see if what I am thinking about using will actually work. The web page with the form and the SOAP definition are shown here.

Image of HolidayService2 web service

So, the correct way to call the GetHolidayDate method appears here.

PS C:\> $proxy.GetHolidayDate("UnitedStates","LABOR-DAY",2013)

Monday, September 2, 2013 12:00:00 AM

Notice that the returned date looks amazingly like the good old-fashioned System.DateTime object we PowerShellers know and love. Once again, because this web service returns .NET objects, I can do some really cool Windows PowerShell stuff. For example, if I want to know how long it is until Labor Day in the United States, I can use the New-TimeSpan cmdlet, as shown here.

PS C:\> New-TimeSpan -Start (get-date) -End ($proxy.GetHolidayDate("UnitedStates","LA

BOR-DAY",2013))

Days              : 196

Hours             : 11

Minutes           : 19

Seconds           : 45

Milliseconds      : 909

Ticks             : 171479859090373

TotalDays         : 198.472059132376

TotalHours        : 4763.32941917703

TotalMinutes      : 285799.765150622

TotalSeconds      : 17147985.9090373

TotalMilliseconds : 17147985909.0373

GG, that is all there is to using a web service to work with holidays. It should be much easier for you than attempting to parse your Outlook calendar. Join me tomorrow when I will talk about a cool function I wrote to determine if a command exists or not in Windows PowerShell.

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: Keep Output from Scrolling Off the PowerShell Console

$
0
0

Summary: Learn how to keep content from scrolling off the Windows PowerShell console.

Hey, Scripting Guy! Question How can I keep content from scrolling off the Windows PowerShell console and, instead, display content a page at a time?

Hey, Scripting Guy! Answer Pipe the results to the More function to display content in the Windows PowerShell console one page at a time. Keep in mind that the More function does not work inside the Windows PowerShell ISE, as shown here.

Get-Command * -CommandType cmdlet | more

Use a PowerShell Function to See If a Command Exists

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, writes a Windows PowerShell function to see if a command exists before calling the command.

Microsoft Scripting Guy, Ed Wilson, is here. This Thursday, February 21, 2013, I will be on the PowerScripting Podcast. I always enjoy talking with Jon and Hal, and while I am not sure of the actual agenda, I do know that we will be talking about my new Windows PowerShell 3.0 Step by Step book. We might talk about the 2013 Scripting Games, Windows PowerShell Saturday, TechEd, or other things I am doing at the Microsoft TechNet Script Center, and of course, the Hey, Scripting Guy! Blog. Other items related to the Windows PowerShell community will doubtlessly arise. One of the really cool things about the PowerScripting Podcast is the chat room. Lots of cool questions come up via the chat room, and at times, there are even funny comments, and it is all I can do to keep from busting out laughing (in fact, sometimes I do end up laughing …) It is like a virtual family reunion (actually better than a virtual family reunion because at least we all have something in common—Windows PowerShell).

Note  My Mentee (or protégé, or Telemachus) Microsoft PFE Ashley McGlone has written an excellent 4-page guide that shows how to use Windows PowerShell to replace RepAdmin, DNSCMD, DCPromo, CSVDE, NETDOM, NLTEST and so on and so on and so on. This is a most excellent reference and is the vision of Windows PowerShell—replace nearly two dozen command-line utilities, each with its own confusing and different syntax, with the consistency of Windows PowerShell. It is way cool and should really help to simplify your life. You can download it from his blog here.

Finding required commands in Windows PowerShell

In a recent PowerTip, I talked about using the Get-ADReplicationConnection cmdlet to find information about AD DS replication. At the time, I wanted to confirm if the cmdlet was available by determining which version of the ActiveDirectory module is installed. Unfortunately, as I checked around, the only way I had to confirm this was to go to a computer with the specific version of the ActiveDirectory module. I had hoped I could check the versionproperty of the module, but unfortunately, the AD DS team did not increment the version number of the module. This is shown here:

PS C:\> ipmo activedirectory

PS C:\> (Get-Module activedirectory).version

 

Major  Minor  Build  Revision

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

1      0      0      0

This leaves me with the situation in which if I want to see if a cmdlet (or a function) exists, I can attempt to use it, and generate an error if the command does not exist. Or I can use the Get-Command cmdlet to search for the cmdlet (or function), but once again, it generates an error if the command does not exist.

So, I decided to write my own function to see if a command exists.

The Test-CommandExists function

To keep from generating an error when I call the Get-Command cmdlet, I use Try / Catch / Finally. There is one problem, and that is that a command not found is not a terminating error, and Try / Catch / Finally only handles terminating errors. Terminating errors are errors that are so bad that Windows PowerShell cannot do anything but halt executing. A non-terminating error is bad, but Windows PowerShell will, by default, continue to the next command. To change this behavior, I need to change the $ErrorActionPreference value from the default value of continueto stop. In this way, the non-terminating error will cause Windows PowerShell to halt execution, therefore permitting the Try / Catch / Finally block to handle the error condition.

But, changing a preference variable without asking is just rude (and is not considered to be a best practice), so, I am going to only change the preference variable while my function runs, and I will change it back when I am done. This is the great thing about Try / Catch / Finally—that I can specify code for each step of the process.

Note  The Finally ALWAYS runs, and it is a perfect place to set things back in their proper condition. I always put my cleanup code in the Finally block.

Therefore, one of the first things I do in my function is store the current $ErrorActionPreference value and then I assign the value of stop to $ErrorActionPreference. These two lines of code are shown here.

$oldPreference = $ErrorActionPreference

$ErrorActionPreference = 'stop'

I create an input parameter that permits me to specify the command I am interested in when I call the function. This line is shown here.

Param ($command)

I use Try to see if the command exists. To do this, I use the If language statement, and I evaluate the condition occurring when I use Get-Command to look for the command. If the command exists, I display this information. The line of code is shown here.

try {if(Get-Command $command){"$command exists"}} 

Now I use Catch to catch the error condition. Therefore, if Get-Command $command results in an error, the code progresses to Catch, and I display a message stating the command does not exist. This is shown here.

Catch {"$command does not exist"}

If an error occurs, the Catch statement displays a message. The code continues to the Finally block. But if an error does not occur, the Catch statement does not execute. However, the Finally block still executes because FINALLY ALWAYS runs. In the Script Block associated with Finally, I set $ErrorActionPreference back to the original value—the value I stored when the function began. This code is shown here.

Finally {$ErrorActionPreference=$oldPreference}

The complete Test-CommandExists function is shown here.

Function Test-CommandExists

{

 Param ($command)

 $oldPreference = $ErrorActionPreference

 $ErrorActionPreference = 'stop'

 try {if(Get-Command $command){"$command exists"}}

 Catch {"$command does not exist"}

 Finally {$ErrorActionPreference=$oldPreference}

} #end function test-CommandExists

When I run the function, the following output is shown.

Image of function output

Making the Test-CommandExists function more friendly

If I configure the function to return Boolean values, instead of strings, I can use the If statement when calling the code. Here is the revision to the function:

Function Test-CommandExists

{

 Param ($command)

 $oldPreference = $ErrorActionPreference

 $ErrorActionPreference = 'stop'

 try {if(Get-Command $command){RETURN $true}}

 Catch {Write-Host "$command does not exist"; RETURN $false}

 Finally {$ErrorActionPreference=$oldPreference}

} #end function test-CommandExists

I do not need to add the RETURN key word, but it makes the code easier to read. The Try section is basic. It is the Catch line where I had to get creative. If I have the Catch section return $false, nothing displays when I call the function as seen here.

If(Test-CommandExists Get-myService){Get-myService}

But, on the other hand, if I have a string that states the command does not exist, it is evaluated as $true and the code attempts to run, and an error generates. Therefore, I decided to use Write-Host here because it does not return to the output stream, and therefore, the calling code does not see it. The use of this function is shown here.

Image of function output

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: Change PowerShell to Display More Info by Default

$
0
0

Summary: Change Windows PowerShell so that it will display more information by default.

Hey, Scripting Guy! Question I pipe information and select certain properties, but the information in my table is truncated. Using Autosize or Wrap does not help. How can I change Windows PowerShell so that it displays the information contained in the property?

Hey, Scripting Guy! Answer You are running into $FormatEnumerationLimit. By default, it is set to 4, but by changing it to a higher number, you can force Windows PowerShell to display additional information. The default behavior is shown here:

PS C:\> Get-Service -Name winmgmt | ft name, DependentServices -AutoSize

Name    DependentServices

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

winmgmt {wscsvc, vmms, SUService, SharedAccess...}

 PS C:\> $FormatEnumerationLimit

4

By assigning a new value to $FormatEnumerationLimit, the remaining DependentServices is displayed. This technique is shown here:

PS C:\> $FormatEnumerationLimit = 8

PS C:\> Get-Service -Name winmgmt | ft name, DependentServices -AutoSize -Wrap

 Name    DependentServices

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

winmgmt {wscsvc, vmms, SUService, SharedAccess, LocationTaskManager, NcaSvc,

        iphlpsvc, IAStorDataMgrSvc}

Note  The value of $FormatEnumerationLimit exists only during the current session. To make the change persist through opening and closing the Windows PowerShell session, or even to survive a reboot, add the new value assignment to your Windows PowerShell profile.

 


Use PowerShell to Find Servers that Need a Reboot

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to find servers that need to be rebooted.

Hey, Scripting Guy! Question  Hey, Scripting Guy! My boss is a Falstaff sort of character, but every once in a while he does actually have a good idea. When he recently logged on to a server console (don’t ask me why my boss was actually logging on to the console of a server), a message popped up that stated the server needed to reboot to complete installing updates.

No one knows how long the server was in that state. So he “challenged” me (a euphuism for telling me to do it) to write a script that would detect any server on the network that needed to be rebooted following installation of hotfixes. I have looked around at several WMI classes, and searched the Hey, Scripting Guy! Blog, but to no avail. I am hoping you can come up with something that does not entail logging on to 1,000 servers and looking for reboot messages that may pop up (that was Falstaff’s idea—“hey, you can use RDP and not have to physically go to the box,” he said).

—YD

Hey, Scripting Guy! Answer Hello YD,

Microsoft Scripting Guy, Ed Wilson, is here. Well, tomorrow February 21, 2013, at 9:30 PM Eastern Standard Time, I will be on the PowerScripting Podcast with Hal and Jon. It will be awesome, and it is a lot of fun. I have been looking forward to this event for weeks now; ever since the Scripting Wife asked me to be on the show (she does all their scheduling). I am not certain about the wisdom of turning on auto update for 1,000 servers. (I would think you would want to do testing, and the like, prior to pushing out the updates and use System Center or WSUS.)

Find servers that need a reboot

To find all servers that need a reboot, I look for the presence of the following registry key:

HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations

The PendingFileRenameOperations key does not appear if there are no files to rename. The rename operation occurs when a file is replaced, and the replacement file is renamed to take the place of the original file. The registry key is shown in the image that follows.

Image of registry editor

Use the ActiveDirectory module to find all servers

An easy way to find all of the servers is to use the ActiveDirectory module and the Get-ADComputer cmdlet. I am feeling a bit lazy today, so I am just going to get all the computers, choose the OperatingSystem attribute, pipe the results to the Where-Object, and pick up anything that looks like server. Here is the code.

Import-Module ActiveDirectory

$servers = Get-ADComputer -Filter * -Properties operatingsystem |

    Where operatingsystem -match 'server'

Use Invoke-Command to find remote registry keys

Ok, now I need to see if the PendingFileRenameOperations registry key exists or not. There are myriad ways of doing this, but lately, I use the Invoke-Command and the Get-ItemProperty cmdlets to do this sort of thing.

Note  To use a local variable on a remote computer requires using $using in Windows PowerShell 3.0 to make the local variable available on the remote computer. Otherwise, the Invoke-Command cmdlet looks for a variable on the remote computer and you will get a path not found, if you are lucky. If you are unlucky, you will get the value of a variable on the remote computer that does not work and it can be difficult to troubleshoot.

The command shown here uses the Invoke-Command cmdlet to run the command in the ScriptBlock against every server stored in the $servers variable. I use the automatic foreach feature of Windows PowerShell 3.0 to get back the name of each server. I specify the ErrorAction (EA is the alias) of silentlycontinue(0 is the enumeration value), so that no errors arise on the console. Remember the ScriptBlock uses Get-ItemProperty to retrieve the registry key value, and if that registry key does not exist, an error arises. The $using:Path brings in the value of the local $path variable, as $using:name brings in the value of $name. The code is shown here.

Invoke-command -ComputerName $servers.name -Ea 0 -ScriptBlock {

Get-ItemProperty -Path $using:path -Name $using:name}

Now, I use the Select-Object to choose only the computer name and to display whether the registry key exists. Here is the code that does this work:

Select-Object pscomputername, @{

 LABEL='RebootRequired';

 EXPRESSION={if($_.PendingFileRenameOperations){$true}}}

When the script runs, it returns a list of computers needing a reboot. This is shown in the following image.

Image of command output

Because the script returns objects with the computer name in it, the results can be piped to the Restart-Computer cmdlet to perform the restarts.

YD, that is all there is to using Windows PowerShell to find all servers that need a reboot. 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: Turn Off PowerShell Module Autoload

$
0
0

Summary: Learn how to turn off Windows PowerShell module auto-loading.

Hey, Scripting Guy! Question I want to disable the Windows PowerShell 3.0 module automatic loading of modules. How do I do this?

Hey, Scripting Guy! Answer Set the value of the $PSModuleAutoloadingPreference automatic variable to none, as shown here.

$PSModuleAutoloadingPreference = "none"

Note   I do not recommend you make this change except in very specific situations and for very specific reasons. The number of Windows PowerShell cmdlets and functions in Windows Server 2012 and Windows 8 would make knowing which module a particular command resided in extremely difficult; with this change in place, you have to specifically load the module prior to using any commands.

Use PowerShell to Perform an Orderly Shutdown of a Server Running Hyper-V

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to shut down all virtual machines on a server running Hyper-V prior to shutting down the server.

Hey, Scripting Guy! Question Hey, Scripting Guy! From time to time I need to shut down one of our servers that is running the Hyper-V role. The problem is that these servers have multiple virtual machines running, and I do not want to crash the virtual machines. So, right now, I use the Hyper-V Manager, target the server running Hyper-V, and right-click every running virtual machine and select Shut Down. I do not mind doing this, but some of the virtual machines are running things like Exchange and it takes them a long time to shut down. So, what should be a simple task of shutting down one of our Hyper-V servers ends up taking nearly an hour—an hour of very boring work, I might add.

—BB

Hey, Scripting Guy! Answer Hello BB,

Microsoft Scripting Guy, Ed Wilson, is here. Well, the snow is all gone. Yep, that is right, we had snow in Charlotte, NC. I am sitting here, sipping a nice cup of tea. I have been experimenting a bit. Today my teapot contains the following recipe: 2 teaspoons (tsp.) of English Breakfast, 1 tsp. of generic green tea, ½ tsp. of organic orange peel, ½ tsp. of licorice root, 1 tsp. of lemon grass, and a crushed cinnamon stick. Let it steep for 5 minutes, and I have a very nice pot of tea. It is sweet enough that I feel it needs no sweetener whatsoever.

Note  In this post, I am using the cmdlets from Windows Server 2012 and the Hyper-V module. I obtained this module on my computer running Windows 8 by downloading and installing the RSAT tools. The Windows 8 RSAT tools are available from the Microsoft Download Center.

 

Find all running virtual machines

BB, the first thing you need to do is to use the Get-VM cmdlet and find all virtual machines that are running on the remote host. To do this, use the Get-VM and pipe the results to the Where-Object cmdlet and filter out for a state that is equal to running. It is not as difficult as it may sound. The command is shown here.

$runningVM = Get-VM -ComputerName $vmhost| where state -eq 'running'

Because you more than likely have more than a single virtual machine running on your remote Hyper-V server, I use the ForEach language statement to walk through the collection of virtual machines that I store in the $RunningVM variable. Inside the loop, I create a WMI Event that uses the Win32_ComputerShutdownEvent WMI class to let me know when each virtual machine shuts down. This portion of the code is shown here.

foreach ($cn in $runningVM)

{

Write-Debug "registering shutdown event for $($cn.name)"

 Register-WmiEvent -Class win32_ComputerShutdownEvent -ComputerName $cn.name `

  -SourceIdentifier $cn.name.tostring()

Once I have registered the event, then I call the Stop-Computer cmdlet to shut down the virtual machine. This code is shown here.

Write-debug "Shutting down $($cn.name)"

  Stop-Computer -ComputerName $cn.name -Force

Because I registered a win32_ComputerShutdownEvent for the virtual machine, an event triggers after the virtual machine shuts down. To pick up this event, I use the Wait-Event cmdlet. Once the computer shuts down, the event triggers. This code is shown here.

Write-Debug "Waiting for shutdown to complete"

  Wait-Event -SourceIdentifier $cn.Name.ToString()}

After all of the virtual machines are shut down, it is time to shut down the Hyper-V host computer (the one that hosts all of the virtual machines). To do this, I use the Stop-Computer cmdlet. This is shown here.

Write-Debug "Shuting down $vmhost"

Stop-Computer -ComputerName $vmhost -Force

Monitor progress of the shutdown

Because I am watching the shutdown of the systems remotely, and I want to know what is happening, I decided to add a series of Write-Debug statements. This is extremely easy to use, and when the script runs without the –debug switch only the default output appears. But when the script runs with the –debug switch, it displays each statement and prompts for the action to take place. This is an interactive type of experience, and it may not be what you want. If you are just wanting more information about each statement without the prompt, then use the Write-Verbose cmdlet instead of Write-Debug. They both work the same—I get them for free as long as I add [cmdletbinding()]

Note   During testing, I noticed that sometimes the script would appear to hang. This happens when the virtual machine stops more quickly than I am able to press “y” to confirm the next step, and therefore, the Wait-Event is waiting for an event that has already occurred.

After testing, I decided I was tired of typing “y” all the time, and so I did a global find and replace of Write-Debug with Write-Verbose. I also decided I needed to remove lingering event objects. So I added the following code.

Get-Event -SourceIdentifier $cn.name.Tostring() | Remove-Event

The revised script is shown here.

[cmdletbinding()]

Param($vmhost = 'hyperv2')

Write-Verbose "getting running VM's on $vmhost"

$runningVM = Get-VM -ComputerName $vmhost| where state -eq 'running'

 foreach ($cn in $runningVM)

{

Write-Verbose "registering shutdown event for $($cn.name)"

 Register-WmiEvent -Class win32_ComputerShutdownEvent -ComputerName $cn.name `

  -SourceIdentifier $cn.name.tostring()

Write-Verbose "Shutting down $($cn.name)"

  Stop-Computer -ComputerName $cn.name -Force

Write-Verbose "Waiting for shutdown to complete"

  Wait-Event -SourceIdentifier $cn.Name.ToString()

  Get-Event -SourceIdentifier $cn.name.Tostring() | Remove-Event}

Write-Verbose "Shuting down $vmhost"

Stop-Computer -ComputerName $vmhost -Force

When I run the script now, the following is shown.

Image of command output

BB, that is all there is to using Windows PowerShell to shut down your virtual machines and then to shut down your Hyper-V server.  Join me tomorrow when I will talk about more cool 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: Count the Number of Commands Exposed by Imported Modules

$
0
0

Summary: Learn how to count the number of commands exposed by imported Windows PowerShell modules.

Hey, Scripting Guy! Question I want to know how many commands are available through the modules I have imported into my current Windows PowerShell session. How can I do this?

Hey, Scripting Guy! Answer Use the Get-Module cmdlet and pipe the results to the Foreach-Object cmdlet (% is an alias). Inside the script block, use the Get-Command cmdlet to return the commands from each module. Send the results to the Measure-Object (measure is an alias) cmdlet, as shown here.

Get-Module | % {Get-Command -Module $_.name} | measure

 

Use PowerShell to Find Installed Video Codecs on Windows 8

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to find installed video codecs on Windows 8.

Hey, Scripting Guy! Question Hey, Scripting Guy! Dude, I am in trouble. Big time! Last week, I installed Windows 8 on the laptops used by all our department heads and front-line supervisors. I thought the responsiveness and the apps would be very useful and a welcome upgrade—I mean, Windows 8 has gotten great reviews, and personally, I love it. But no!!!!! Come to find out, I guess the only thing these people use their laptops for is to watch DVDs when they are on the road. (I imagine they also check their email, but they could use their phones to do that.) Anyway, I never noticed that Windows 8 does not ship with a CODEC to enable someone to watch a DVD. Huh? I mean, get real, this is ridiculous. Personally, I like Media Player and have used it for years, but not if it is going to get me fired. I need a way to find out if a laptop has a particular CODEC, AND I need to find a DVD CODEC now. Can you help me?

—ML

Hey, Scripting Guy! Answer Hello ML,

Microsoft Scripting Guy, Ed Wilson, is here. WOW, ML, I am sorry you are in trouble. I can imagine that I might have gotten into a similar predicament because I also would not consider watching a DVD on my laptop as a mission-critical use case scenario, and it would have fallen out of the test specs.

Yes, there has been some mixed messaging around playing DVDs on Windows 8. A quick search via Bing reveals lots of blog posts that state that Media Center would be in Windows 8, and not so much that says it will not be in Windows 8. And since we have had Media Center in various editions of Windows for several releases now, I can also expect that you might have missed it. The good news is that you can download the Windows 8 Media Center Pack for Windows 8 Pro for $9.99 USD. The details are available via this web page. Also, if you are not running Windows 8 Pro, we have an upgrade that is available—this upgrade also includes Windows 8 Media Center. I actually did this for one of the Scripting Wife’s computers and it worked very slick, and kept all of her settings (of course, I backed everything up first, just in case).

Finding installed video codecs

To find video codecs installed on your computers, use the Win32_VideoCodec WMI class. You can query it via the old Get-WmiObject cmdlet, as shown here.

Get-WmiObject win32_codecfile -Filter 'group = "video"'

But because you are targeting computers running Windows 8, you can also use the Get-CimInstance cmdlet, as shown here.

Get-CimInstance win32_codecfile -Filter 'group = "video"'

Querying multiple remote computers

To query multiple remote computers, by using the Get-WmiObject cmdlet, you can simply add multiple computer names, and specify the appropriate credentials. This technique is shown here.

Get-WmiObject win32_codecfile -Filter 'group = "video"' -Credential iammred\administrator -ComputerName ws1,ws2,ws3

To query multiple computers by using the CIM cmdlets, first create a cim session, and then do the query. This is shown here.

$session = New-CimSession -ComputerName ws1,ws2,ws3 -Credential iammred\adminIstrator

Get-CimInstance win32_codecfile -Filter 'group = "video"' -CimSession $session

Examine the data

After you have the data, you can examine it by piping it to the Format-List cmdlet and selecting all of the properties. You can then peruse the output to see what properties are most useful for you. Here is an example.

Image of command output

ML, that is all there is to using WMI to examine video codecs. Join me tomorrow for the Weekend Scripter, when I will talk about creating test log files on Saturday and about parsing those log files on Sunday. It will be fun, I promise.

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: Find Dynamic Parameters for Certain PowerShell Providers

$
0
0

Summary: Learn how to find out about dynamic parameters for certain Windows PowerShell providers.

Hey, Scripting Guy! Question How can I easily find out about parameters that are added to cmdlets when they are used with a specific Windows PowerShell provider?

Hey, Scripting Guy! Answer Use the Get-Help cmdlet and look for the Help topic related to the provider. Near the bottom of the article is a section about dynamic parameters. The following illustrates this technique for the FileSystem provider.

Get-Help FileSystem

Weekend Scripter: Creating a Sample Log File by Using PowerShell

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, shows how to create sample textual log output by using Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, is here. Well, it is the weekend. It seems like it has been a rather long week, but I guess in reality it was no longer than any other week. I did get a cool book that finally showed up by the package delivery van. I started on it last night; it is a great book about the philosophy of mathematics—neat stuff, actually. Oh, the other cool thing that happened this week is the Scripting Brother’s new baseball book is now available for pre-order. I read the advanced reading copy that my brother sent me, and it is actually pretty good (but hey, I am not a huge baseball fan).

Creating a simple Windows PowerShell script to write a log file

Tomorrow, I plan to write an article about a cool Windows PowerShell feature I found the other day. Therefore, I need script that will create a log file and append to the log file over a certain period of time. To do this, I decided to use three Windows PowerShell cmdlets. The first is Get-Date, the next one is Add-Content, and the last one is Start-Sleep.

First, I define a couple of variables. The first variable is the path to the log file, and the second one is how many lines I want to write to the log file. These two variables are shown here:

$path = "C:\fso\mylogfile.log"

$numberLines = 25

Now, I use the forlanguage statement to add the lines to the log file. I begin counting at zero and add lines until I have reached the number specified by $numberLines. This is shown here.

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

{

To create the line of text to add to the log file, I use a variable to hold the results of a little parameter substitution. The first parameter is the line number and the second one is the date. When getting the date, I use a pretty cool trick: I convert the datetime object to a string and choose only the hour, minute, and second. This is shown here.

$SampleString = "Added sample {0} at {1}" -f $i,(Get-Date).ToString("h:m:s")

Now the Add-Content cmdlet writes to the log file. If the log file does not exist, it creates the log file, and then adds to it. This line is shown here.

add-content -Path $path -Value $SampleString -Force

Once I write to the log file, I pause the script for 100 milliseconds. To do this, I use the Start-Sleep cmdlet. This code is shown here.

Start-Sleep -Milliseconds 100

The complete script is shown here.

$path = "C:\fso\mylogfile.log"

$numberLines = 25

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

{

 $SampleString = "Added sample {0} at {1}" -f $i,(Get-Date).ToString("h:m:s")

 add-content -Path $path -Value $SampleString -Force

 Start-Sleep -Milliseconds 100

 }

The created log file is shown here.

Image of log file

Fixing the code

Everything is pretty groovy, except for one small thing: I do not want the line numbers starting over at 25. This makes things a bit confusing, if I run the script over multiple days. So, I need a way to persist the value of $i. I could write it to the registry, but that is overkill. Since I am already writing to a text file, I decide to create a file that will simply store a single number. I call the file MyLogFileInit.txt.

Now I need to test to see if the file exists. If it does, I read it to get my starting number for line numbers, but if the file does not exist, I begin at zero. Seems like a great place to use Test-Path. Here is what I came up with.

if(Test-path $init)

 {$initLine = Get-Content $init}

Else {$initline = 0}  

The last major revision I do is persist the value of $i to the file. I use Out-File for this. I always want to overwrite what is there, and I only want the single number stored there. Here is what I use.

$i | Out-File -FilePath $init -Force

The revised script is shown here.

$path = "C:\fso\mylogfile.log"

$init = "C:\fso\mylogfileInit.txt"

[int]$numberLines = 25

if(Test-path $init)

 {$initLine = Get-Content $init}

Else {$initline = 0} 

For ([int]$i=$initline;$i -le ($numberLines + $initLine);$i++)

{

 $SampleString = "Added sample {0} at {1}" -f $i,(Get-Date).ToString("h:m:s")

 add-content -Path $path -Value $SampleString -Force

 Start-Sleep -Milliseconds 100

 }

$i | Out-File -FilePath $init -Force

Here is the revised log file. You will notice that I now continue adding to my line numbers and avoid the recycled numbers.

Image of log file

Well, that is about all that I want to do today. I wish to get back to my book. Hope this gives you ideas for playing around with Windows PowerShell. I invite you back tomorrow when I will use this script to show you something really cool.

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 Send Output to Both the PowerShell Console and a Variable

$
0
0

Summary: Learn how to send output to both the Windows PowerShell console and to a variable.

Hey, Scripting Guy! Question How can I easily send Windows PowerShell output to both the console and to a variable?

Hey, Scripting Guy! Answer Use the Tee-Object cmdlet and specify a variable name, as shown here.

PS C:\> gps | select name -Last 1 | Tee-Object -Variable a

Name

----

ZeroConfigService

 

PS C:\> $a

Name

----

ZeroConfigService

>

Weekend Scripter: Two Way-Cool PowerShell Text File Tricks: Tail and Wait

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about two way-cool new Windows PowerShell parameters: Tail and Wait.

Microsoft Scripting Guy, Ed Wilson, is here. Some things in Windows PowerShell are just so cool that I get all carried away with them and lose focus on whatever I am supposed to be doing. It happens that way sometimes. Luckily, it is the weekend, so I am covered for frittering away my time. But hey, it is for a good cause.

Note Today I talk about reading from a log file as it updates. I also talk about reading from the end of a log file. I am using the script I wrote yesterday to add to the log file as I watch for updates. See yesterday’s article, Weekend Scripter: Creating a Sample Log File by Using PowerShell,for that script and for a discussion of the script.

Reading x number of lines from beginning or from end of log

For log files that log to the end of the file, the most recent information is at the bottom of the file. This can be a problem if I don’t know how many lines of stuff are in the log. Oh, I can do it, but even using Windows PowerShell, it requires a bit of code. Not anymore, because we now have the tailparameter that will retrieve a specific number of lines from the very bottom of the file. This technique is shown here.

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 1

Added sample 125 at 4:0:42

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 2

Added sample 123 at 4:0:42

Added sample 125 at 4:0:42

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Tail 3

Added sample 121 at 4:0:42

Added sample 123 at 4:0:42

Added sample 125 at 4:0:42

If I am interested in the first lines in the file, I use the –headparameter and specify how many lines I want to obtain. This is shown here.

PS C:\> Get-Content -Path C:\fso\mylogfile.log -Head 2

Added sample 0 at 3:46:40

Added sample 1 at 3:46:40

But what about when the action is still occurring?

If the action is still occurring, that is, it is still being written to the log file, use the –waitparameter of Get-Content. The command is shown here.

Get-Content -Path C:\fso\mylogfile.log -Tail 1 –Wait

In this way, I retrieve the last written log file entry as it occurs.

To test this, I run the command above in the Windows PowerShell console. The Windows PowerShell console waits for new lines to be written to the file. I then switch to the Windows PowerShell ISE and run my Add-LogV2.ps1 script so that new entries are added to the file.

I switch back to the Windows PowerShell console and watch the entries add new lines to the Windows PowerShell console. The result is shown in the following image.

Image of command output

Well, that is about it for monitoring a log file. Join me tomorrow when I will talk about parsing the System event log.

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: Learn About PowerShell Format Files

$
0
0

Summary:  Learn about Windows PowerShell format files. 

  Where can I get good information about the various *format.ps1xml files that Windows PowerShell uses?

 On Windows PowerShell 3.0, after you have updated Help by running the Update-Help command, use Help to display the format Help content. Here is the command that I use to do this:

help format -Category helpfile


Use PowerShell to Find Windows 8 Modern App Updates

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to find information about Windows 8 Modern App updates.

Hey, Scripting Guy! Question Hey, Scripting Guy! I love my Windows 8 laptop, and I also love my Windows RT Surface. One of the things that is a bit annoying is that it seems every time I turn on the computer, or my Surface, the Store says that updates are available. I mean, dude, this is crazy. Every day it is a different app. I am wondering if this information is logged anywhere. Maybe it's my imagination, and it's not really every day. Can you help?

—EP

Hey, Scripting Guy! Answer Hello EP,

Microsoft Scripting Guy, Ed Wilson, is here. Well, things are humming right along in Charlotte, North Carolina. The weekend was warm, but a bit of rain kind of sogged our day. Today, I am anxiously watching for the package delivery truck, and so every time I hear a low engine rumble on my street, I jump up and look out the window to see if a delivery arrives. Why? Well, I am looking for my new laptop. It might not be here … after all, I have been watching for nearly a month, but hey, one can always hope. It is bound to be a day closer than it was yesterday.

Examining the event log

App updates are handled via Windows Update Client. As such, all automatic download information is recorded in the System event log. When the updates are downloaded, an Event ID 17 generates from the WindowsUpdateClient source. This means that the information you seek is available in the Windows event log. Examining one of these entries is always good first step. This is shown here.

 

Now that I know what I am looking for, I can use Windows PowerShell to query the System event log, look for Event ID 17, and return the event log entries. Here is the code I derived by using the Get-EventLog cmdlet.

Get-EventLog -LogName system -Newest 20 -InstanceId 17

When I run the command, a lot of information is displayed that I am not particularly interested in seeing. The command and the associated output are shown here.

Image of command output

The output is basically useless. Except for one thing—it lets me know that my initial supposition is incorrect. It appears that more than Windows Update has an event ID 17 defined. This is shown here.

25184 Feb 20 10:57  Information Microsoft-Windows...           17 Protocol NIC...

So, I need to change my query just a little bit. I need to specify the source of the event, which is: Microsoft-Windows-WindowsUpdateClient. The problem with this is that it is an awful lot of typing. Maybe it will accept a wildcard … hmmmm, I wonder. So, I modify my command as shown here.

Get-EventLog -LogName system -Newest 20 -InstanceId 17 -Source *update*

As seen in the image that follows, the query works, but it does not help on the actual information front, I am still missing the information I need.

Image of command output

Here is my revised query.

Get-EventLog -LogName system -Newest 20 -InstanceId 17 -source *update*| select timewritten,message | ft -AutoSize –Wrap

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

Image of command output

EP, that is all there is to using Windows PowerShell to find information about Windows 8 modern app updates. Join me tomorrow when I will talk about finding which apps are requiring the most updates.

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: Get a Quick Update on Event Log Status via PowerShell

$
0
0

Summary: Use Windows PowerShell to get a quick event log status update.

Hey, Scripting Guy! Question How can I quickly obtain a status update on my event logs? Things such as the retention policy, overflow action, and numbers of event in the logs?

Hey, Scripting Guy! AnswerUse the Get-EventLog cmdlet with the * after it, as shown here.

Get-EventLog *

Viewing all 3333 articles
Browse latest View live


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