Wednesday, November 25, 2015

Here is my custom Powershell prompt

Get-Help about_Prompts
<#
LONG DESCRIPTION
    The Windows PowerShell command prompt indicates that Windows PowerShell
    is ready to run a command:

        PS C:\>

    The Windows PowerShell prompt is determined by the built-in Prompt
    function. You can customize the prompt by creating your own Prompt
    function and saving it in your Windows PowerShell profile.
#>

One of my biggest issues with the default prompt is that I work with a lot of nested folders and network shares. It makes the path so long because the path is in there. So I change my prompt to just show the current folder and place the full path in the tittle bar.

One other thing I do is add basic command logging. I would use transcripts, but I don't want something that verbose. So I just save my last command to a text file whenever I run it.

The last thing I so is calculate where in the history the next command will be and add that to my prompt.

Here is my prompt function:

$PSLogPath = ("{0}{1}\Documents\WindowsPowerShell\log\{2:yyyyMMdd}-{3}.log" -f $env:HOMEDRIVE, $env:HOMEPATH,  (Get-Date), $PID)
Add-Content -Value "# $(Get-Date) $env:username $env:computername" -Path $PSLogPath
Add-Content -Value "# $(Get-Location)" -Path $PSLogPath

function prompt
{
    $LastCmd = Get-History -Count 1
    if($LastCmd)
    {
        $lastId = $LastCmd.Id
       
        Add-Content -Value "# $($LastCmd.StartExecutionTime)" -Path $PSLogPath
        Add-Content -Value "$($LastCmd.CommandLine)" -Path $PSLogPath
        Add-Content -Value "" -Path $PSLogPath
    }

    $nextCommand = $lastId + 1
    $currentDirectory = Split-Path (Get-Location) -Leaf
    $host.UI.RawUI.WindowTitle = Get-Location
    "$nextCommand PS:$currentDirectory>"







Monday, November 16, 2015

Powershell: Script injection with ScriptBlock.CheckRestrictedLanguage Method

I just had a post about importing hashtables from files. It basically loads the file into a script block and executes it. 

$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create($content)
$scriptBlock.CheckRestrictedLanguage([string[]]@(), [string[]]@(), $false)
Write-Output (& $scriptBlock

If that sounds dangerous, that is because it is. Your module or script my be trusted, but it may be loading files that are not trusted. This could be a stealthy way for an attacker to use your script.

I took the extra step of using $scriptBlock.CheckRestrictedLanguage([string[]]@(), [string[]]@(), $false) to make sure the hashtable is not containing Powershell commands. There is one important gotcha to be aware of with this command. The arguments are not intuitive. 

Lets take this command. Here is how the function is defined using a C# sample:

public:
void CheckRestrictedLanguage (
        IEnumerable allowedCommands,
        IEnumerable allowedVariables,
        bool allowEnvironmentVariables
)

The first argument is the allowed commands and the second is the allowed variables. One could reasonably assume that a $null value for the allowed commands would mean that nothing is allowed.

If you look at my code, I create an empty string array. That may look like a very strange thing to do and I kind of agree. This is because a $null value indicates that it should allow some default commands to execute. The only way to know this one is to read the documentation very closely. https://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock.checkrestrictedlanguage(v=vs.85).aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-2

By using an empty list of strings, I do not allow any Powershell commands. When importing a hashtable, this is exactly what I want.

Sunday, November 08, 2015

Powershell: Importing hashtable from file or a psd1 file

Have you ever wanted to import a hashtable from a file? A module manifest that is saved in a *.psd1 file is a hashtable. If you ever wanted to read the meta data in it, this trick may help.

You import the contents into a script block, validate the script block, execute it and capture the resulting hashtable into a variable. Here is the sample code below:


$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$scriptBlock = [scriptblock]::Create($content)
$scriptBlock.CheckRestrictedLanguage([string[]]@(), [string[]]@(), $false)
Write-Output (& $scriptBlock)



If you target a module manifest, you can access all the attributes in it. 

Name                           Value
----                           -----
Copyright                      (c) 2015 Kevin.Marquette. All rights reserved.
CompanyName                    Self
GUID                           6ab379f9-41ed-4c1e-beda-7855d1c1e3c8
Author                         Kevin.Marquette
FunctionsToExport              *
VariablesToExport              *
RootModule                     .\my_module.psm1
AliasesToExport                *
CmdletsToExport                *
ModuleVersion                  1.0.1 

The CheckRestrictedLanguage will throw an error if it finds any powershell commands in the hashtable. Because you are executing code from a un-trusted source in the middle of your script, you should validate it.

There is a second quick and dirty way to do the same thing without the validation. I almost don't want to mention it because it is so dangerous. So if you see this in the wild, know that there is a better way.

$HashTable = Invoke-Expression (Get-Content $Path -raw)

This blindly executes a file as if it was a script. This is just asking to be exploited. Think CSS cross site or SQL injection type of vulnerability. 

Monday, October 26, 2015

Using Powershell to unlock Active Directory account Unlock-ADAccount

OK, this is an easy one. There is a command in the ActiveDirectory module that already takes care of this.

Unlock-ADAccount kevmar 

So quick to type and it works so well. But unlocking the account is only part of the issue. The real question is why is it locked. If your lucky, the user will say they just miss-typed it a few times and you can let it go. Sometimes you find an account that just locks over and over. Here is a simple command that works really well in my environment to find the source of the issue:

function Get-ADAccountLockInfo
{
    [cmdletbinding()]
    param(
        $ComputerName = "servername",
        [pscredential] $Credential
    )
   
    $WinEvent = @{
        ComputerName = $ComputerName
        FilterHashtable = @{logname='security';id=4740}
    }
   
    if($Credential)
    {
        $WinEvent.Credential = $Credential
    }
   
    Get-WinEvent @WinEvent | %{
        [pscustomobject]@{
            TimeCreated  = $_.timecreated;
            Username     = $_.properties[0].value;
            ComputerName = $_.properties[1].value
        }
    }
}


Replace the server name with the name of the domain controller that is the PDC emulator. If you do not know what one that is, you can run this once for each domain controller. What it does is queries the security log for event id 4740. It then tells you who is locked at what time and where the source is.

I added a credentials option because I often execute this cross domain. The $WinEvent is a hashtable that I splat to the Get-WinEvent. That makes it easy for me to add the credential to the hashtable when if it is defined.

Any idea how to troubleshoot this with out knowing the event ID to look for? You start with the cell phone and clear any WiFi settings and emails settings. Then do the same for any tablets they have (even at home). Then exit lync and outlook on their laptop and desktop. Open 'Credential Manager' and delete all the saved credentials (do a start menu search for it). Then check all desktops and servers where they may have used RDP to connect to a session but just disconnected when they are done. Then check for services, jobs, tasks, and reporting services connections that may be using that credential.

Yeh, searching the security log is much easier.

Saturday, October 10, 2015

Powershell: how to return value from function?

Returning a value from a function is really easy in Powershell. Sometimes it is too easy if you are not careful about it. One important detail to keep in mind is that Powershell is built around placing objects on the pipe. This makes functions work differently in Powershell than it does from different languages. You are also not limited to just one return value.

Here is a minimal function:

function Test-Function
{
    "Hello World!"

Calling this places a string of "Hello World!" onto the pipe. You can catch it in a variable if you want or pipe it to some other command. The better way to do it is to use the Write-Output cmdlet. It makes it more clear as to what you are doing do debugging and troubleshooting become easier. 

function Test-Function
{
    Write-Output "Hello World!"
}

You can call Write-Output multiple times in a function to return multiple items. You could place it into a loop that runs 10 times and it will output 10 objects. I mention this because Powershell also supports the return keyword but it acts different. When a function gets to any return statement, it exits the function. 

function Test-Function
{
    return "Hello World!"
}

I prefer Write-Output over return but do use return when it is needed. 

I also want to mention that Write-Host is something that you see in scripts from time to time, but it is for host only output. So it is hard to capture into a variable and does not go into the pipe. In most cases, Write-Output is the better choice.

Powershell: how to call a function?

Sometimes it is the simplest thing that can be the most frustrating when learning a new language. Calling a custom function in Powershell can be one of those if you come from a different language. If you are having an issue with this, you have come to the right place. You are probably looking at something like this and wondering why it wont work:

TestFunction($Name, $Count)


If your coming from another language, this looks like it would be perfectly normal. The gotcha in Powershell is that the coma and the parenthesis are not needed here for a function call and actually mean something else. In this example it defines an array.
  
Before I go any further, let's define TestFunction:

function TestFunction ($name, $count)
{
    Write-Output "Name is '$name' and Count is '$count'"
}

It is a simple function that takes two parameters. While this works as a function and looks like something you know, let's do it the Powershell way:

function TestFunction
{
    param($name, $count)

    Write-Output "Name is '$name' and Count is '$count'"
}

So now we have a function that takes two arguments. The first argument is name and the second argument is count. Here are the correct ways to call that function:

TestFunction $Name $Count
TestFunction -name $Name -count $Count
TestFunction -name "Kevin" -count

Now that we know the correct way to make that call and know what the function looks like, I can explain why our initial call fails to work. Because it is the equivalent to this call:

$Array = ($Name,$Count)
TestFunction -name $Array -count $null

So you were basically creating an array that contained your two variables and passing it in as an array to the first argument. You were also leaving the second argument as a null value. Now that you see it, it looks like a silly thing to do. No wonder things were not working as you expected. 


Monday, September 28, 2015

Powershell: how to concatenate strings?

I was going to write a post on how to concatenate or join string, but the Hey, Scripting Guy! Blog already has a great write up on that.

Monday, June 22, 2015

Quick and Dirty Powershell Modules

So you have built some awesome scripts and turned them into advanced functions. Whats next? It is time to put them into a module. It can be a lot easier than you realize. Lets go step by step and build our first module to hold all of your advanced functions.

# Start in our profile powershell folder
CD ~\Documents\WindowsPowershell

# Create a folder for our module and functions
MD Modules\Other\Functions

# Create a module manifest
$Manifest = @{
    Path        = ".\Modules\Other\Other.psd1"
    RootModule  = ".\Other.psm1" # Module loader
    Author      = "Kevin Marquette"
    Description = "Odds and ends"   
}
New-ModuleManifest @Manifest -Verbose

# Create our module loader (that loads our advanced functions)
$ModuleLoader = @'
  $moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path

  Write-Verbose "Importing Functions"
  # Import everything in the functions folder
  "$moduleRoot\Functions\*.ps1" |
      Resolve-Path |
      Where-Object { -not ($_.ProviderPath.Contains(".Tests.")) } |
      ForEach-Object { . $_.ProviderPath ; Write-Verbose $_.ProviderPath}
'@

Set-Content -Value $ModuleLoader -Path .\Modules\Other\Other.psm1

# Now create a single file for each advanced function and place it in the functions folder
# Sample function
$TestFunction = @'
   function Test-Other
   {
        [cmdletbinding()]
        param()
        Write-Output "Hello World!!"
   }
'@
Set-Content -Value $TestFunction -Path .\Modules\Other\functions\Test-Other.ps1

# Load it and test it out
Import-Module Other -Verbose -Force
Test-Other


You could easily place all your functions into the Other.psm1 file and everything would still work. But this creates a framework that makes your functions easy to manage. That functions folder can now be a dumping ground for all your advanced functions. If you out grow this module, you can create a new one and just move the functions over.

The module loader is what enables that. Later we will add pester tests and this already accounts for this. This is a pattern that I seen used by other Powershell MVPs. It has greatly simplified my function management.

Friday, June 05, 2015

JoinDomainOrWorkgroup 1323 error unable to update the password

I am using the Win32_ComputerSystem WMI object to join machines to a domain with JoinDomainOrWorkgroup. Then I ran into an issue on XP/2003 where I would get this 1323 return code and it would fail. This looked to work find for server 2012 so I started digging.

From the MSDN documentation, error 1323 means  "error unable to update the password".  My first set of searches implied that the time was out of sync between the servers. Manual join worked fine and after syncing the time to the DC before the join, I had the same issue.

$wmi.JoinDomainOrWorkgroup("Domain","Password","UserName",$null,3

Then I found a comment here that said add the domain to the username.

$wmi.JoinDomainOrWorkgroup("Domain","Password","Domain\UserName",$null,3

And then it worked.

Monday, May 25, 2015

Solving the hardest problems with powershell. Where to go next?

Sometimes you run into a problem and you don’t know where to go next. When I run into an issue, it often becomes an obsession for me to solve it. It is easy to say google it, but sometimes you need to be able to look at your problem from different points of view for google to actually help you. I have been wiring Powershell for a long time and this is how I tackle those hard to solve problems.

First use get-help, get-command, show-command, get-member, and Format-List * to try and discover a command and get information about object. The more advanced you get with Powershell, the more you will use these commands. So build that habit. (Run update-help to get the most recent updates).

Then turn to google. Something like “Powershell thing I am trying to do”. There are a lot of good samples and old samples out there. The old samples will still work but you may miss the new way to solve that problem if you are on server 2012/windows 8. This is why those first commands are important. If you want examples, get-help command –examples.

Then search for a command line way to do it. If a command or a tool exists, then that’s the easiest way. Depending on what I find, I may look at other solutions and come back to this one.

If this is a standalone application, figure out how it stores its settings. Registry, text file or database. Knowing this will give you a direction. I’ll mention how to flesh these out later if you are unsure at this point.

Then search for a WMI solution. Powershell and WMI play really well together. Windows is an API based OS and a lot of those APIs are exposed in WMI or CIM. People have been working with WMI for a long time and lots of examples exists. I solve a lot of problems with WMI. One I know the object to look at, get-member and format-list * help me explore it. There is also a show-object script floating around that may also help here.

Then google for how to solve your problem with the registry. The registry really controls a lot of things and I would bet a lot of the Powershell script for system configuration are just setting a value in the registry if it is not using WMI.

From here, I look for a VBScript or C#/.Net solution. If it feels like something an admin should be able to do, I will search harder for VBscript. Odds are that someone has solved this issue before. VBScript is easy to translate into simpler Powershell once you have done it a few times. C# and .Net offer a lot of power but you may be diving into some serious code at this point. You can take this to an extreme and look for Win32 API or system calls (rundll32 type stuff). This is never fun but sometimes that is where the solution is.

Another approach is using sysinternals to figure out how the system does what it is doing. Procmon is great. It watches every file change and registry change that your system does. So fire that up, and make your change. Then start hunting for what the system did when you make that change. If SQL is involved, then SQL profiler is also a must have tool.

Look for a GPO solution. If you can find one, remember that most of group policy is just setting registry keys. If there is an ADM file, you can dig into that for the actual key.

While you are doing your hunting, there are a few complications to be aware of. Is it a per user setting or a per machine setting. User profiles can easily be adjusted by the user, but if you are remote or running with different creds, there are often other roadblocks to deal with.


Also, there is nothing wrong with asking for help either. I just wanted to give you a direction. Even if this is all gibberish today. The more advanced you get at this, the deeper down the rabbit hole you can go. I have used every one of these techniques at some point to solve a problem.