Server Core Page File Management with Powershell

A quickie for tonight.

(Heh heh...)

Microsoft is really pushing to make this command line-only, Server Core and Powershell thing happen. No more GUI. Everything needs to get done on the command line. Wooo command line. Love it.

So... how the heck do you set the size and location of the paging file(s) without a GUI? Could you do it without Googling Binging? Right now?

You will be able to if you remember this:

$PageFileSizeMB = [Math]::Truncate(((Get-WmiObject Win32_ComputerSystem).TotalPhysicalMemory + 200MB) / 1MB)
Set-CimInstance -Query "SELECT * FROM Win32_ComputerSystem" -Property @{AutomaticManagedPagefile="False"}
Set-CimInstance -Query "SELECT * FROM Win32_PageFileSetting" -Property @{InitialSize=$PageFileSizeMB; MaximumSize=$PageFileSizeMB}

The idea here is that I'm first turning off automatic page file management, and then I am setting the size of the page file manually, to be static and to be equal to the size of my RAM, plus a little extra. If you want full memory dumps in case your server crashes, you need a page file that is the size of your physical RAM plus a few extra MB.

You could have also done this with wmic.exe without using Powershell, but when given a choice between Powershell and not-Powershell, I pretty much always go Powershell.

Did I mention Powershell?

Powershell RemoteSigned and AllSigned Policies, Revisited

A while back I talked about Powershell signing policies here.  Today I'm revisiting them.

Enabling script signing is generally a good idea for production environments. Things like script signing policies, using Windows Firewall, etc., all add up to provide a more thorough defense-in-depth strategy.

On the other hand, one negative side-effect that I've seen from enforcing Powershell signing policies is that it breaks the Best Practices Analyzers in Windows Server, since the BPAs apparently use scripts that are unsigned. Which is strange, since Microsoft is usually very good about signing everything that they release. I assume that they've since fixed that.

I'd consider the biggest obstacle to using only signed Powershell scripts to be one of discipline. But maybe that in itself is a good thing - if only the admins and engineers who are disciplined enough to put their digital signature on something are allowed to run PS scripts in production, perhaps that will cut down on the incidents of wild cowboys running ill-prepared scripts in your environment, or making a quick change on the fly to an important production script, etc. Could you imagine having to re-sign your script every time you changed a single line? That seems like the perfect way to encourage the behavior that scripts are first perfected in a lab, and only brought to production once they're fully baked and signed.

The next obstacle is getting everyone their own code signing certificate. This means you either need to spend some money getting a boat-load of certs from a public CA for all your employees, or you need to maintain your own PKI in your own Active Directory forest(s) for internal-use-only certificates.  This part alone is going to disqualify many organizations. Very rare is the organization that cares about properly signing things in their IT infrastructure.  Even rarer is the organization that actually does it, as opposed to just saying they want everything to be properly signed.  "It's just too much hassle... and now you're asking me to sign all my scripts, too?"

I also want to reinforce this point: Like UAC, Powershell script execution policies are not meant to be relied upon as a strong security measure. Microsoft does not tout them as such. They're meant to prevent you from making mistakes and doing things on accident. Things like UAC and PS script execution policies will keep honest people honest and non-administrators from tearing stuff up.  An AllSigned execution policy can also thwart unsophisticated attempts to compromise your security by preventing things such as modifying your PS profile without your knowledge to execute malicious code the next time you launch Powershell. But execution policies are no silver bullet. They are simply one more thin layer in your overall security onion.

So now let's play Devil's advocate. We already know the RemoteSigned policy should be a breeze to bypass just by clearing the Zone.Identifier alternate NTFS data stream. How do we bypass an Allsigned policy?

PS C:\> .\script.ps1
.\script.ps1 : File C:\script.ps1 cannot be loaded. The file C:\script.ps1 is not digitally signed. The script will not execute on the system.

Script won't execute?  Do your administrator's script execution policies get you down?  No problem:

  • Open Notepad.
  • Paste the following line into Notepad:
Powershell.exe -NoProfile -Command "Powershell.exe -NoProfile -EncodedCommand ([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes((Get-Content %1 | %% {$_} | Out-String))))"
  • Save the file as bypass.bat.
  • Run your script by passing it as a parameter to bypass.bat.

PS C:\> .\bypass.bat .\script.ps1

... And congratulations, you just bypassed Powershell's execution policy as a non-elevated user.

So in conclusion, even after showing you how easy it is to bypass the AllSigned execution policy, I still recommend that good execution policies be enforced in production environments. Powershell execution policies are not meant to foil hackers.

  1. They're meant to be a safeguard against well-intentioned admins accidentally running scripts that cause unintended consequences.
  2. They verify the authenticity of a script. We know who wrote it and we know that it has not been altered since they signed it.
  3. They encourage good behavior by making it difficult for admins and engineers to lackadaisically run scripts that could damage a sensitive environment.

Anatomy of a Powershell Advanced Function

This is the template I'm using for my Powershell 301: Anatomy of a Powershell Advanced Function class. In this online virtual class, I'll be discussing the Powershell Advanced Function feature line by line. Hopefully this will help instill good scripting techniques for scripters who are new to Advanced Functions or Powershell in general.

 

#Requires -Version 3
#Requires -Module ActiveDirectory
Function New-Cmdlet
{
<#
.SYNOPSIS
	This Cmdlet uses an approved verb from Get-Verb, comment-based help, #Requires
    statements, and it follows the format of an advanced Powershell function.
.DESCRIPTION
	This Cmdlet uses an approved verb from Get-Verb, comment-based help, #Requires
    statements, and it follows the format of an advanced Powershell function.
    This Cmdlet is designed only as a teaching tool to show good form for 
    writing Powershell v3 Cmdlets, also known as "Advanced Functions."
.PARAMETER ComputerName
    This describes the ComputerName parameter. If there are any defaults or extra
    attributes of the parameter as listed in the Param() block below, they will
    automatically be populated here. Notice that we use ComputerName here, and not
    ComputerNames or Hostname. That's because every other Cmdlet uses the parameter
    ComputerName with no S, even if we intend to supply multiple names, so we should
    stay consistent with that. Make it feel as much like an official Powershell
    Cmdlet as you can.
.EXAMPLE
    Get-Content C:\Hosts.txt | New-Cmdlet -Verbose

    This runs New-Cmdlet on each hostname in the Hosts.txt file.
.EXAMPLE
    "Server1","Server2","Server3" | New-Cmdlet

    This runs New-Cmdlet on Server1, Server2 and Server3.
.EXAMPLE
    New-Cmdlet -ComputerName Server1,Server2 -WhatIf -Verbose

    This tells what would happen if you ran New-Cmdlet on Server1 and Server2.
.INPUTS
    System.String[]

    This should be an array (a list) of one or more computer hostnames that you
    want to run this Cmdlet on. Pipeline input is accepted.
.OUTPUTS
    Nothing... yet!
.LINK
    If any relevant links, put 'em here.
.NOTES
    Notes. Author, date, whatever. You don't have to include every single one of 
    these event-based help categories, but it never hurts.
#>
	[CmdletBinding(SupportsShouldProcess=$True)]
	Param([Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)][ValidateLength(3,30)][String[]]$ComputerName)

    BEGIN
    {
        # Run one-time set-up tasks here, like defining variables, etc.
        Set-StrictMode -Version Latest
        Write-Verbose -Message "Cmdlet is starting. You won't see this message unless you used -Verbose."
    }
    PROCESS
    {
        # The process block can be executed multiple times as objects are passed through the pipeline into it.
        ForEach($computer In $ComputerName)
        {
            If($PSCmdlet.ShouldProcess($computer))
            {
                Write-Verbose -Message "Beginning Process block on $computer"          
                $computer
            }        
        }
    }
    END
    {        
        # Finally, run one-time tear-down tasks here.
        Write-Verbose -Message "Running End block."
    }
}

My Powershell Profile Just Went Full-Glitz

My cat woke me up extremely early this Saturday morning with the incessant meowing and carpet-scratching that signals either her boredom, or an empty food dish.

So I got up, made some coffee, put some meat crackers into kitty's bowl, and then started tinkering with my Powershell profile... now it looks like this every time I launch PS: 

Powershell Profile

It all started a couple weeks ago when I watched a Channel9 video where Jeffrey Snover was playing with Powershell, and I noticed that he had changed his error text color to green. I'm guessing like so:

$Host.PrivateData.ErrorForegroundColor = "Green"

I don't know why he configured his error messages to be green. Maybe it's just because it's easier to see than the default red.  But I like to imagine the idea is to promote positive feedback... like elementary school teachers marking their student's incorrect homework answers with another color of pen besides a red pen... because red ink makes the kids feel bad.

Anyway, as I started playing with text colors and title bar text and whatnot, it occured to me that all these settings would just revert to defaults after I closed this Powershell session. So how do we make such changes permanent?

The Powershell Profile!

Just type $Profile into Powershell right now, and it will tell you the full path of your very own PS profile. It should be something like this:

C:\Users\Ryan\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

That script gets executed first thing every time you launch PS. It may not exist yet - you have to create it.  Just type Notepad $Profile and Notepad will open that file up, or prompt you to create it if it doesn't already exist.

I'm still thinking of more neat gizmos to throw in here, but this is good for now. The  weather information comes from the Yahoo Weather web API, and the ServerFault rep information comes from the StackExchange API. *Swoon...* REST APIs are so dreamy...

The StackExchange API gives you 300 anonymous calls per day per IP (more if you authenticate.)  There is a basic amount of error handling so that if you can't connect to one or the other of the web APIs to get the data for whatever reason, it will just replace the appropriate string with [Error connecting to weather API], and so on. You'd want to put a short timeout on the API calls too... Powershell doesn't need any help being slow to load!

And without further ado, here's the code:

Set-StrictMode -Version Latest
[String]$WOEID           = "2355944" # Where on earth ID for Arlington TX
[String]$WelcomeName     = "Ryan"
[Xml]$WeatherAPIResponse = $Null
$StackExAPIResponse      = $Null
[String]$WelcomeBanner   = [String]::Empty
[String]$WeatherString   = [String]::Empty
[String]$StackExString   = [String]::Empty

Try
{
    $WeatherAPIResponse = Invoke-WebRequest http://weather.yahooapis.com/forecastrss?w=$WOEID -TimeoutSec 3 -ErrorAction Stop
    If($WeatherAPIResponse -NE $Null -AND $WeatherAPIResponse.PSObject.Properties.Match('rss').Count)
    {
        $WeatherString = "Current weather in $($WeatherAPIResponse.rss.channel.location.city), $($WeatherAPIResponse.rss.channel.location.Region): $($WeatherAPIResponse.rss.channel.item.condition.temp)°, $($WeatherAPIResponse.rss.channel.item.condition.text), $($WeatherAPIResponse.rss.channel.atmosphere.humidity)% humidity"
    }
    Else
    {
        Throw
    }
}
Catch
{
    $WeatherString = "[Error connecting to weather service.]"
}

Try
{
    $StackExAPIResponse = Invoke-WebRequest https://api.stackexchange.com/users/104624?site=serverfault -TimeoutSec 3 -ErrorAction Stop
    If($StackExAPIResponse -NE $Null -AND $StackExAPIResponse.PSObject.Properties.Match('Content'))
    {
        $StackExString = "Current ServerFault rep: $($(ConvertFrom-Json $StackExAPIResponse.Content).Items.Reputation) total,  $($(ConvertFrom-Json $StackExAPIResponse.Content).Items.reputation_change_day) today, $($(ConvertFrom-Json $StackExAPIResponse.Content).Items.reputation_change_week) this week"
    }
    Else
    {
        Throw
    }
}
Catch
{
    $StackExString = "[Error connecting to StackExchange.  ]"
}


$WelcomeBanner      = @"
            .ooooooo            Welcome back, $WelcomeName!
          oooooooooooo          $WeatherString
        ooooo      ooooo        $StackExString
       oooo          oooo       
       ooo            .oo       
   oooooooooo          ooo      
  ooooooo.oooo.        oo.      
 ooo        .o.        ooooo    
ooo                    ooooooo  
oo                    .oooooooo 
oo                    oo     ooo
ooo                           oo
.oo                          ooo
 oooo                        oo.
  .oo myotherpcisacloud.com oo 
    .oooooooooooooooooooooooo  

"@

Write-Host $WelcomeBanner -ForegroundColor Cyan

Powershell: Set-StrictMode -Version Latest

Set-StrictMode is a wonderful Cmdlet that you should use in every script you write.  It will save you hours of debugging and frustration from things like fat-fingered variable and property names.  Here is what version 2 of Set-StrictMode does:

-- Prohibits references to uninitialized variables (including uninitialized variables in strings).
-- Prohibits references to non-existent properties of an object.
-- Prohibits function calls that use the syntax for calling methods.
-- Prohibits a variable without a name (${}).

If you apply Set-StrictMode to some of your old scripts, you'll be surprised at how many new errors it will throw at you. This is good though, because it encourages you to write safer, cleaner code with less bugs.  Number 2 on the list above was giving me fits today, however.  Let me give you an example.

$Users = Get-ADUser -Filter * -Properties *

Now let's say that I am interested in the EmployeeId attribute of the users. Even though I specified -Properties *, the user objects in the collection may or may not have a property called EmployeeId. The user object will not have an EmployeeId property that is blank or null.  If the attribute is not populated in Active Directory, the Cmdlet omits the property entirely.  So in a script without Strict Mode, I could just do

Foreach($User In $Users) { If($User.EmployeeId) { ... } }

And it would function as expected, running the code block if the user had an EmployeeId, and skip it otherwise. But with Strict Mode, you'll see a lot of this:

Property 'EmployeeId' cannot be found on this object; make sure it exists.
At line:1 char:9
+ $User. <<<< EmployeeId
    + CategoryInfo          : InvalidOperation: (.:OperatorToken) [], RuntimeException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

Even if I did

$Users = Get-ADUser -Filter { EmployeeId -NE $Null } -Properties *

or

$Users = Get-ADUser -Filter * -Properties * | Where-Object { $_.EmployeeId -NE $Null }

I would still get the errors when running through the Foreach loop, even though that should have given me only the users with EmployeeIDs. So I need a way to test for the existence of an object property, rather than just assuming that a null value returned when the property is referenced is good enough.

If($User -NE $Null -AND $User.PSObject.Properties.Match('EmployeeId').Count) { ... }

This works. The -AND operator in Powershell works like a "short circuit" && operator in most programming languages, meaning that if the first expression does not satisfy the requirements for entering the code block, then the second expression is not evaluated. This is perfect, because if I accidentally feed a null user object to the code above, I would have gotten the same error about the $User object not containing a PSObject property.

Alright, back to scripting!