Recently I have put all my most commonly used PowerShell scripts into a Module. By bundling them together into a module, I remove the need for individual script files to be maintained and updated on all servers in the network.
Doing this has caused a few problems of its own, so here is the process I used to get it working.
The first thing I did was copy the module to a shared location that can be accessed easily by everything on the network.
Next, anything that needs to use my module needs to be made aware of it. There are 2 steps to this process.
1. Edit the environment variable “PSModulePath” to include the path to the module
2. Import the module
Both steps can be accomplished by editing the PowerShell profile. There are several PowerShell profiles; I have chosen to use the profile for All Users, All Hosts. This ensures the module will be available no matter how PowerShell is used. This is important for me because as well as running commands locally I also call commands using external applications like Nagios.
More information on PowerShell profiles can be found here:
The All Users, All Hosts profile is located here:
C:\windows\system32\WindowsPowerShell\v1.0\profile.ps1
The profile.ps1 file will not exist by default, so it is usually necessary to create it (new text file called profile.ps1)
1. Edit the environment variable “PSModulePath” to include the path to the module This done by adding the following line to the PowerShell profile:
$env:psmodulepath = $env:psmodulepath +";\\ServerShare\Folder\modules"
2. Import the module
This done by adding the following line to the PowerShell profile:
Import-Module ModuleName
Launching PowerShell will give you an error message at this point. This is because PowerShell sees the module as coming from an external source and doesn't trust it. PowerShell uses Internet Explorer's Zone Policy to see if it trusts the file, script or module you are attempting to run.
This can be manually edited through:
Internet Options >> Security tab >> Local Intranet >> Sites
Here we can add the external source as a trusted site, \\ServerShare
Doing this will only take effect for the currently logged on user, and will still cause an error when an external program attempts to run something using PowerShell - eg. Nagios
To get around this we must add the trusted site to the local machine's trusted zone policy. This can be done by creating two new registry keys.
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\ServerShare
Name: file
Type: DWORD
Value: 1
and
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\ServerShare
Name: file
Type: DWORD
Value: 1
I have a lot of servers that require this change, so I wrote some PowerShell that will do it for me, here is the script:
$registryPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\ServerShare"
$registryPath1 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\ServerShare"
$name = "file"
$value = "1"
If (!(test-path $registryPath )) {
New-Item -Path $registryPath -Force | Out-Null
New-ItemProperty -Path $registryPath -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
}
If (!(test-path $registryPath1 )) {
New-Item -Path $registryPath1 -Force | Out-Null
New-ItemProperty -Path $registryPath1 -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
}
Using these settings let me use all the commands that are exposed by my module and I can leave the execution policy set at Remote Signed.
Below are some good articles that helped me out with this process:
Running PowerShell Scripts From An UNC Path (Share)
Signing PowerShell Scripts
Can't import a module from a UNC path
Update or Add Registry Key Value with PowerShell
UPDATE - 19-10-2015
I've been using this process quite a bit, and found that it doesn't work in all circumstances. For newer versions of Internet Explorer, there is an extra step to editing the Local Intranet zones policy:
Internet Options >> Security tab >> Local Intranet >> Sites >> Advanced
When using these later versions of IE, the registry changes don't work, and the module on a UNC path still causes an error when the profile tries to import it. I can stop the error from being displayed by changing the Execution Policy to Bypass, however, I wasn't keen on implementing that as a solution across our estate. Instead I'm now using a different method.
Copy-Item \\ServerShare\Folder\modules\ModuleName -Destination D:\scripts\modules -Recurse -Force
$env:psmodulepath = $env:psmodulepath +";D:\scripts\modules"
Import-module ModuleName
This takes a copy of the module and puts it into a local folder. The module is then imported from this local folder, which removes the need for PowerShell to treat it as a remote script.
Wednesday, 19 August 2015
Wednesday, 14 January 2015
Use PowerShell to search Windows Event Logs
PowerShell is an excellent tool for searching through Windows event logs. I find myself using it more and more these days as it enables me to find the information I need much quicker than using the filter feature of the Event Viewer snap in.
The cmdlet to use for searching the event logs is get-eventlog. For the full help file from PowerShell, enter the following
help Get-EventLog -Full
The get-eventlog cmdlet uses the switch -LogName. This is used to specify the event log you want to search, eg System, Application etc.
To get a list of available event logs, enter
Get-EventLog -List
or
Get-EventLog -LogName *
You can then list all events from that event log with the command Get-EventLog -LogName LogName
Eg,
Get-EventLog -LogName System
This will return everything from the event log, probably hundreds or even thousands of events, so the next job is to filter for the events you are interested in.
We can examine an individual event log to get an idea of how to filter
Get-EventLog -LogName System -Newest 1
This command will return the most recent System event log
The information displayed is a subset of the complete data available for this event log. PowerShell will automatically select the columns to display so that it fits easily on the screen. To see everything, we need to pipe the output to the format-list cmdlet.
Get-EventLog -LogName System -Newest 1 | Format-List -Property *
This command returns all the properties and values for this event log
Now you can see all the familiar properties of the event, such as EventID, EntryType, Time Written etc. I can now filter my result based on one or more of these properties.
For example,
show all events where the message contains "the service entered the stopped state"
show all events where the event id equals 41 and the date is 10 Jan 2015
show all events where the EntryType is error, the source is Asp.Net or .Net runtime and the date is between 01 Jan - 10 Jan 2015
All of this is achieved by piping the results of get-eventlog to the where-object cmdlet
Get-EventLog -LogName System | Where-Object { $_.Message -like "*the service entered the stopped state*" }
In this example you can see that I have used the Message property of the event and the -like operator to match it to the text I am looking for.
Get-EventLog -LogName System | Where-Object { $_.EventID -eq 41 -and $_.TimeWritten -like "01/14/2015*" }
In this example you can see that I have used the EventID and TimeWritten properties of the event
$_.EventID -eq 41
$_.TimeWritten -like "01/14/2015*"
The -and operator links these two properties together
Get-EventLog -LogName System -After (Get-Date -Date '1/1/2015') -Before (Get-Date -Date '10/1/2015') | Where-Object { $_.EntryType -eq "Error" -and ($_.Source -like "Asp.Net*" -or $_.Source -like ".net runtime*") }
In this example I use the date property of the get-eventlog results to filter the events before passing them to the where-object cmdlet. This is done using the -before and -after switches. After that, the where-object command is used in the same way as the previous examples.
The only difference is the two $_.Source properties are surround by brackets (). This is necessary so that the -or operator applies to only those two entries. I could add more $_.Source properties inside the brackets with additional -or operators if I wanted to increase this list beyond 2.
Understanding the PowerShell operators is key to getting the results you want, this link contains useful information about operators
http://technet.microsoft.com/en-gb/library/hh847759.aspx
Finally, you can export your results using any of PowerShell's export commands. I find export-csv works well:
Get-EventLog -LogName System | Where-Object { $_.Message -like "*the service entered the stopped state*" } | Export-Csv C:\scripts\events.csv
The cmdlet to use for searching the event logs is get-eventlog. For the full help file from PowerShell, enter the following
help Get-EventLog -Full
The get-eventlog cmdlet uses the switch -LogName. This is used to specify the event log you want to search, eg System, Application etc.
To get a list of available event logs, enter
Get-EventLog -List
or
Get-EventLog -LogName *
You can then list all events from that event log with the command Get-EventLog -LogName LogName
Eg,
Get-EventLog -LogName System
This will return everything from the event log, probably hundreds or even thousands of events, so the next job is to filter for the events you are interested in.
We can examine an individual event log to get an idea of how to filter
Get-EventLog -LogName System -Newest 1
This command will return the most recent System event log
The information displayed is a subset of the complete data available for this event log. PowerShell will automatically select the columns to display so that it fits easily on the screen. To see everything, we need to pipe the output to the format-list cmdlet.
Get-EventLog -LogName System -Newest 1 | Format-List -Property *
This command returns all the properties and values for this event log
Now you can see all the familiar properties of the event, such as EventID, EntryType, Time Written etc. I can now filter my result based on one or more of these properties.
For example,
show all events where the message contains "the service entered the stopped state"
show all events where the event id equals 41 and the date is 10 Jan 2015
show all events where the EntryType is error, the source is Asp.Net or .Net runtime and the date is between 01 Jan - 10 Jan 2015
All of this is achieved by piping the results of get-eventlog to the where-object cmdlet
Get-EventLog -LogName System | Where-Object { $_.Message -like "*the service entered the stopped state*" }
In this example you can see that I have used the Message property of the event and the -like operator to match it to the text I am looking for.
Get-EventLog -LogName System | Where-Object { $_.EventID -eq 41 -and $_.TimeWritten -like "01/14/2015*" }
In this example you can see that I have used the EventID and TimeWritten properties of the event
$_.EventID -eq 41
$_.TimeWritten -like "01/14/2015*"
The -and operator links these two properties together
Get-EventLog -LogName System -After (Get-Date -Date '1/1/2015') -Before (Get-Date -Date '10/1/2015') | Where-Object { $_.EntryType -eq "Error" -and ($_.Source -like "Asp.Net*" -or $_.Source -like ".net runtime*") }
In this example I use the date property of the get-eventlog results to filter the events before passing them to the where-object cmdlet. This is done using the -before and -after switches. After that, the where-object command is used in the same way as the previous examples.
The only difference is the two $_.Source properties are surround by brackets (). This is necessary so that the -or operator applies to only those two entries. I could add more $_.Source properties inside the brackets with additional -or operators if I wanted to increase this list beyond 2.
Understanding the PowerShell operators is key to getting the results you want, this link contains useful information about operators
http://technet.microsoft.com/en-gb/library/hh847759.aspx
Finally, you can export your results using any of PowerShell's export commands. I find export-csv works well:
Get-EventLog -LogName System | Where-Object { $_.Message -like "*the service entered the stopped state*" } | Export-Csv C:\scripts\events.csv
Monday, 12 January 2015
PowerShell and IIS 6
IIS 6
Windows Server 2003 running IIS 6 does not support the latest version of PowerShell, and will not allow the use of the WebAdministration module or the most recent IIS cmdlets(see this site for WebAdministration Cmdlets http://technet.microsoft.com/en-us/library/ee790599.aspx)
To use PowerShell with IIS 6, you must use WMI, specifically the IIS WMI provider. To do this, use the Get-WmiObject cmdlet.
The WMI namespace is root/Microsoftv2. The following link gives a list of classes that can be used:
http://msdn.microsoft.com/en-us/library/ms525265(v=vs.90).aspx
WMI Classes
I experimented with the different classes to find the properties that I needed. For example IISWebServer will give information on each site, IISWebServerSetting will give more detailed information on each site. IISWebVirtualDir will give information on virtual directories and applications and IISWebVirtualDirSetting will again give more detail on virtual directories and applications.For example, try running each of these commands, then view the results to see how the output changes.
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServerSetting
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebVirtualDir
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebVirtualDirSetting
The next step is to use the where-object and select-object cmdlets to display only the results you want.
Eg.
This command will return a list of sites by site ID
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer | Select-Object -ExpandProperty Name
Whereas this command will return a specific site by specifying the site ID
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer | Where-Object { $_.Name -like "W3SVC/123456789" }
Also, this command will return a specific site by specifying the site ID along with all the settings for that site
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServerSetting | Where-Object { $_.Name -like "W3SVC/123456789" }
I was interested in listing all Physical Paths that were being used by each site in IIS. For this I need the IISWebVirtualDirSetting class and the Path property
Eg.
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebVirtualDirSetting | Select-Object -ExpandProperty Path
I can also add the -unique switch to eliminate any duplicates, and pipe the results to sort-object to put the output in alphabetical order
Eg,
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebVirtualDirSetting | Select-Object -ExpandProperty Path -Unique | Sort-Object
Script
I put this all together into a script that will output the site name followed by a list of all Physical Paths in use by IIS. One of the challenges here was identifying a class that contained both the site name and physical path. This was not possible with one class, so I had to do it in stages.
I obtained the Name property from IIsWebServer class. This is the Site ID, and I can use this to filter both ServerComment property from IIsWebServerSetting class for a user friendly website name, and the path property from IIsWebVirtualDirSetting for the Physical Path
Script text
$WebSiteID = Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer | Select-Object -ExpandProperty Name
ForEach ( $Site in $WebSiteID )
{$WebSiteName = Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServerSetting | Where-Object { $_.Name -like "$site" } | Select-Object -Expandproperty ServerComment
write-host "`r`n" $WebSiteName
$AppPath = Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebVirtualDirSetting | Where-Object { $_.Name -like "$site/*" } | select -expandproperty path
$AppPath = $AppPath | select-object -unique | sort-object
$AppPath
}
One more thing to note is that I use -ExpandProperty rather than -Property to filter the results from the where-object and select-object cmdlets. This is to convert the output to a string. If I don't do this, I get the object itself, rather than a string containing the objects. To see what this actually means, try running these two commands and notice the difference in the results and the available methods and properties.
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer | Select-Object -Property Name | Get-Member
Get-WmiObject -Namespace "root/MicrosoftIISv2" -Class IIsWebServer | Select-Object -ExpandProperty Name | Get-Member
Subscribe to:
Posts (Atom)