Search Results for: $profile

More $Profile Tricks — Automatically Opening an Admin Window

I run as a limited user during my normal work, but I always keep one or more Admin windows open. These are logged in to my Domain Administrator account, running "As Administrator". And I make sure I can tell that I'm running in that window by setting the colour scheme with a nice, dark red, background. Hard to miss! (I'll show you how to do that in my next post. ) So, how do I do all that? Well, it starts by automatically opening a PowerShell window when I first log on, as described earlier here.

When that starts, I include code in my $Profile to first check how many PowerShell windows are already running, so I don't start opening more if I don't need them.

$PSH = Get-Process PowerShell

Simple, and let's me get a count with $PSH.count. If this is the first PowerShell window I've opened ($PSH.count -lt 2) and this isn't already an admin window, then I open an admin window. Let's break this down: First, am I running as an Administrator?

# First, initialize $Admin to false 
$Admin = $False

# Then, find out who we are... 
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent() 
$p = New-Object system.security.principal.windowsprincipal($id)

# Find out if we're running as admin (IsInRole). 
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)){ 
   $Admin = $True 
}

Now, a function to run an elevated window as the domain administrator.

Function Start-myAdmin () { 
# Get admin credentials 
$AdminCred = Get-Credential 
        -UserName TreyResearch\Administrator ` 
        -Message "Enter your password for the Administrator account: " 
# Start an elevated window, but with designated creds. 
Start-Process PowerShell.exe ` 
       -Credential $AdminCred ` 
       -ArgumentList "Start-Process PowerShell.exe -Verb RunAs" ` 
       -NoNewWindow

}

Good. So we're going to want to start that admin window automatically when we first logon. We do that by checking the count of open PowerShell windows ($PSH.count)

# If this isn't already an Admin window, and we don't have lots of other PowerShell 
# windows open, then we start an Admin PowerShell window. 
if ( ! $Admin  ) {
   cd $home
   if ($Psh.count -lt 2 ) {
      Start-myAdmin
   }
}

Oh, and we'll want an alias to be able to open up another one whenever we need it.

Set-Alias Admin -Value Start-myAdmin

 

More $profile madness

As usual, a minor glitch in my workflow has me mucking around with my $profile. It all started because I normally run Word, Excel, and other Office applications from the command line (as I do many non-MS applications.) To do this, I have functions and aliases in my $profile. These let me start Word, for example, by typing "word" on the PowerShell command line along with any filenames I want to open. So far, so good. But I recently started using Office 2013 on some machines, and this broke my functions for those machines. And at least one of them is running the 640-bit version of office. Hmmm. That could just be a problem. Now I could manually edit the functions in those $profiles, but that's just kludgy. I like to have a single $profile that I load on any new machine I work on. So, rather than the hard-coded paths, which were never really elegant, but worked fine when I only had one version running, it's time to be a bit more general. So, I added the following function to my $profile:

Function Find-Office {
   # if on 32-bit, or running 64-bit office on 64-bit machine, this will find office
   $Global:[[code]]czo2Olwib2ZmaWNlXCI7e1smKiZdfQ==[[/code]] = Get-ChildItem `
              -file "Outlook.exe" `
              -path "C:\Program Files\Microsoft Office\" `
              -recurse
    if (! $office) {
    # If we didn't find it there, look down the (x86) path. But only if we didn't find it
    $Global:[[code]]czo2Olwib2ZmaWNlXCI7e1smKiZdfQ==[[/code]] = Get-ChildItem `
              -file "Outlook.exe" `
              -path "C:\Program Files (x86)\Microsoft Office\" `
              -recurse
    }
}

Now, a couple of comments here -- I made the $Office variable Global so that I could get at the value outside of my profile, since there's a fair amount of information and properties in it.  The other thing is that while I'm searching for Outlook, it really could have been any of the core Office apps that I know get installed every time I install Office. If you're running Office Home & Student edition, however, you'd best choose a different executable, such as "winword.exe".

So, now that we know where Office is, we can extract the path to it:

$OffPath = (Find-Office).DirectoryName

(note, we're still inside my $profile, but we won't bother to make $OffPath global, since we can easily get it again if we need it. )

Finally, the functions that our aliases will call to start the core Office applications:

function Run-Word { & "$OffPath\WinWord.exe" $args}
function Run-Excel { & "$OffPath\Excel.exe" $args }
function Run-OneNote { & "$OffPath\OneNote.exe" $args }
function Run-Outlook { & "$OffPath\Outlook.exe" $args }

The aliases are simply calls to these functions in the form:

set-alias word Run-Word

Nothing fancy there. But now I've got easy command line access to my core Office applications. (As you can see, I don't use PowerPoint any more than I have to. :))

Customizing PowerShell–Using $Profile

A copy of the profile described here is at: Charlie_Profile

Probably one of the first scripts any new Windows PowerShell user writes is a custom profile. Your PowerShell profile ($profile in PowerShell speak), is run every time you open a PowerShell window, and it allows you to do a lot of different things to set up your environment the way you want it. Actually, though, there are four profiles that affect your PowerShell window, as described in this MSDN article. There are arguments for which profile you should be editing, but my personal preference is to use the most specific one:

%UserProfile%\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

The only problem is, that’s not really were “My Documents” is for me. So, how to be sure you always work on the right one, wherever it’s located? Easy – let PowerShell tell you!

First, let’s see if we have a $profile yet:

PS1> Test-Path $profile
False

Nope. So, how to make one. First,let’s create the path:

PS1> mkdir (Split-Path $profile)

    Directory: C:UserscprDocuments

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        12/22/2010  12:05 PM            WindowsPowerShell

Now,use your favourite plain text editor to create the file. In my case that would be:

PS1> gvim $profile

And here you can start to build your customizations for PowerShell. There are lots of possibilities, including changing the colouring, updating the title bar, creating aliases and functions that are always available. Really the possibilities are nearly endless.

In my own case, I like to distinguish between a regular window and an elevated one, so I change the colour of all my regular PowerShell windows to white with dark blue text (works better for screen shots), and my elevated windows to dark red with white text. I also use a multiline prompt, and do some other customizations.

Here’s my full $profile:

# This is Charlie”s custom PS Profile…
#
# ModHist: 26/05/2007 – Initial
#        : 08/06/2007 – Added clear-host and winzip alias
#        : 10/06/2007 – Changed OS detection
#        : 22/07/2007 – support for running from local home
#        : 14/03/2008 – changed to Microsoft.PowerShell_profile.ps1
#        : 16/03/2008 – added productID test for Vista
#        : 24/03/2009 – added mapdrives for admin shell
#        : 17/10/2010 – minor cleanup before sending to VanTUG
#        : 26/02/2011 - modified to post cleaner on WordPress
#
#***************************************************************************
#

# Get the name of this version...
$CallingProfile = $myInvocation.mycommand.path
$FileRegEx = "(?<name>.*)(?<dot>\.)(?<extension>[pP][sS]1)"
$CallingProfile -match $FileRegEx

# Find out who we are...
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object system.security.principal.windowsprincipal($id)
$admin=$p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)

# Get System WMI info -- Get it once, use it multiple
$SysWMI =  Get-WmiObject Win32_OperatingSystem
$hostname = $(hostname).tolower()
$otherway = ($ENV:computername).tolower()

$ProdID=(gi ''HKLM:/Software/Microsoft/Windows NT/CurrentVersion'').getvalue(''ProductID'')

# Build a hashtable with information about versions, etc.
$SystemHash = @{}  # Initialize the variable as a hashtable
$SystemHash["Build"] = $SysWMI.BuildNumber
$SystemHash["SPNumber"] = $SysWMI.CSDVersion
$SystemHash["Caption"] = $SysWMI.Caption
$SystemHash["SKU"] = $SysWMI.OperatingSystemSKU
$SystemHash["Architecture"] = $SysWMI.OSArchitecture
$SystemHash["Hostname"] = $SysWMI.CSName.ToLower()
$SystemHash["Arch"] = $ENV:Processor_Architecture
$SystemHash["ProductID"] = $ProdID

# We’ll need the build number to persist in the environment
$Build = $SystemHash["Build"]

switch -regex ($SystemHash["Build"]) {
2600 { $ver="XP" }
3790 { if ($SystemHash["Caption"] -match "XP") {
$ver = "XPx64"
} else {
$ver = "Server 2003"
}
}
6000 { $ver="Vista" }
6001 { if ($SystemHash["Caption"] -match "Vista" ) {
$ver="Vista"
} else {
$ver="Server 2008"
}
}
7600 { if ($SystemHash["Caption"] -match "Windows 7" ) {
$ver="Windows 7"
} else {
$ver="Server 2008 R2"
}
}
7601 { if ($SystemHash["Caption"] -match "Windows 7" ) {
$ver="Windows 7 SP1"
} else {
$ver="Server 2008 R2 SP1"
}
}
}

if ($SystemHash["Arch"] -eq "AMD64" ) {
$64bit=$true
$32bit=$false
} else{
$64bit=$false
$32bit=$true
}

# Find out if we''re running as admin (IsInRole)
# if we are, change the window colour and set effectivename variable
# But only on Windows Vista or Later
if ($admin -and ($build -ge 6000 ))
{
$effectivename = "Administrator"
$host.UI.RawUI.Backgroundcolor="DarkRed"
$host.UI.RawUI.Foregroundcolor="White"
clear-host
$maps=''C:\Windows\system32\mapdrives.cmd''  #cmd file to map standard drives
if (Test-Path $maps) {& $maps }
} else {
$effectivename = $id.name
$host.UI.RawUI.Backgroundcolor="White"
$host.UI.RawUI.Foregroundcolor="DarkBlue"
clear-host
}

$host.ui.rawui.WindowTitle = $effectivename + "@" + $HostName +" >"
function global:prompt {
if ( $effectivename -eq "Administrator" ) {
write-host ("[") -nonewline -foregroundcolor red
write-host ($Hostname) -nonewline -foregroundcolor Magenta
write-host ("]") -nonewline -foregroundcolor Red
Write-Host ([string]$(get-location) +":") -foregroundcolor Green
write-host ("PSH>" ) -nonewline -foregroundcolor White
} else {
write-host ("[") -nonewline -foregroundcolor red
write-host ($Hostname) -nonewline -ForegroundColor Magenta
write-host ("]") -nonewline -foregroundcolor Red
Write-Host ([string]$(get-location) + ''> '') -ForegroundColor DarkGreen
write-host ("PSH>" ) -nonewline -foregroundcolor DarkBlue
}
return " "
}

# function to elevate a command. Finally.
function sudo ([string]$file, [string]$arguments) {
$psi = new-object System.Diagnostics.ProcessStartInfo $file;
$psi.Arguments = $arguments;
$psi.Verb = "runas";
$psi.WorkingDirectory = get-location;
[System.Diagnostics.Process]::Start($psi);
}

# Now, a function to edit the executable script without knowing where it is.
function fvi
{
param($script )
$s = (get-command -ea silentlycontinue $script).definition
if ( $s ) { gvim $s }
else { "$script not found" }
}

# Update the path to make life easier, and add a function while we''re at it...
$ENV:Path="$ENV:PATH;" + "U:\psbin;" + "$ENV:HOMEDRIVE" + "$ENV:HOMEPATH" + "\psbin;"
function path {
write-host $env:path
}

# Now, start to build some aliases. These may be machine specific, so be careful...

if ($64bit) {
set-alias winzip -value "C:\Program Files (x86)\WinZip\WinZip32.exe"
set-alias word -value "C:\Program Files (x86)\Microsoft Office\Office14\WinWord.exe"
set-alias excel -value "C:\Program Files (x86)\Microsoft Office\Office14\Excel.exe"
set-alias OneNote -value "C:\Program Files (x86)\Microsoft Office\Office14\OneNote.exe"
set-alias Visio -value "& mstsc C:\Program Files (x86)\RemotePackages\Visio_2010.rdp"
set-alias hyperv -value "C:\Program Files\Hyper-V\virtmgmt.msc"
set-alias vm -value "C:\Program Files\Hyper-V\vmconnect.exe"
} else {
set-alias winzip -value "C:\Program Files\WinZip\WinZip32.exe"
set-alias word -value "C:\Program Files\Microsoft Office\Office14\WinWord.exe"
set-alias excel -value "C:\Program Files\\Microsoft Office\Office14\Excel.exe"
set-alias OneNote -value "C:\Program Files\Microsoft Office\Office14\OneNote.exe"
set-alias Visio -value "& mstsc C:\Program Files\RemotePackages\Visio_2010.rdp"
}

set-alias edit -value "C:Windows\gvim.bat"
set-alias vi -value "C:windows\gvim.bat"

# set-alias ping test-connection # disabled. More annoying than useful

# The following is useful if you have automatic startup PowerShell windows...
if ( ! $Admin ) {
cd $home
}

There, that’s complicated enough for most uses. :-)
But hopefully you’ll find some useful stuff in this that you can use for your own $profile.

Charlie.

Resizing the PowerShell Console

Windows 10's support for high DPI displays is much better than previous iterations of Windows, but there are still some times it gets a bit confused. One such problem occurs when you have multiple high DPI displays or two displays of different sizes. If you move PowerShell console windows between displays or log back in after being logged out for a while, you can end up with a scrunched up PowerShell window. Nothing I had to deal with when all I had was a pair of standard FullHD monitors, but ever since I got my Surface Book, and connected it to a 28 inch 4k monitor, I've had periodic problems. Very annoying when your PowerShell window changes to 37 characters wide and 7 lines long!

 

The fix is to reset the window size. Now I can do this graphically (right click on the title bar, select Properties, and then the Layout tab), but that's a nuisance at best, and besides, the whole idea of using the GUI to fix a console just isn't right. The answer is to leverage the built-in $host variable:

$host | Get-Member

   TypeName: System.Management.Automation.Internal.Host.InternalHost

Name                   MemberType Definition
----                   ---------- ----------
EnterNestedPrompt      Method     void EnterNestedPrompt()
Equals                 Method     bool Equals(System.Object obj)
ExitNestedPrompt       Method     void ExitNestedPrompt()
GetHashCode            Method     int GetHashCode()
GetType                Method     type GetType()
NotifyBeginApplication Method     void NotifyBeginApplication()
NotifyEndApplication   Method     void NotifyEndApplication()
PopRunspace            Method     void PopRunspace(), void IHostSupportsInteractiveSession.PopRunspace()
PushRunspace           Method     void PushRunspace(runspace runspace), void IHostSupportsInteractiveSession.PushRunspace(runspace runspace)
SetShouldExit          Method     void SetShouldExit(int exitCode)
ToString               Method     string ToString()
CurrentCulture         Property   cultureinfo CurrentCulture {get;}
CurrentUICulture       Property   cultureinfo CurrentUICulture {get;}
DebuggerEnabled        Property   bool DebuggerEnabled {get;set;}
InstanceId             Property   guid InstanceId {get;}
IsRunspacePushed       Property   bool IsRunspacePushed {get;}
Name                   Property   string Name {get;}
PrivateData            Property   psobject PrivateData {get;}
Runspace               Property   runspace Runspace {get;}
UI                     Property   System.Management.Automation.Host.PSHostUserInterface UI {get;}
Version                Property   version Version {get;}
  

OK, there's some interesting bits there, but the one that looks most promising is UI. So:

 $host.UI | get-member


   TypeName: System.Management.Automation.Internal.Host.InternalHostUserInterface

Name                    MemberType Definition
----                    ---------- ----------
Equals                  Method     bool Equals(System.Object obj)
GetHashCode             Method     int GetHashCode()
GetType                 Method     type GetType()
Prompt                  Method     System.Collections.Generic.Dictionary[string,psobject] Prompt(string caption, string message, System.Collection...
PromptForChoice         Method     int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection[System.Management...
PromptForCredential     Method     pscredential PromptForCredential(string caption, string message, string userName, string targetName), pscredent...
ReadLine                Method     string ReadLine()
ReadLineAsSecureString  Method     securestring ReadLineAsSecureString()
ToString                Method     string ToString()
Write                   Method     void Write(string value), void Write(System.ConsoleColor foregroundColor, System.ConsoleColor backgroundColor, ...
WriteDebugLine          Method     void WriteDebugLine(string message)
WriteErrorLine          Method     void WriteErrorLine(string value)
WriteInformation        Method     void WriteInformation(System.Management.Automation.InformationRecord record)
WriteLine               Method     void WriteLine(), void WriteLine(string value), void WriteLine(System.ConsoleColor foregroundColor, System.Cons...
WriteProgress           Method     void WriteProgress(long sourceId, System.Management.Automation.ProgressRecord record)
WriteVerboseLine        Method     void WriteVerboseLine(string message)
WriteWarningLine        Method     void WriteWarningLine(string message)
RawUI                   Property   System.Management.Automation.Host.PSHostRawUserInterface RawUI {get;}
SupportsVirtualTerminal Property   bool SupportsVirtualTerminal {get;}
  

Hmmm. Even more interesting stuff. I can tell I'm going to be doing some poking around in here! But, for our purposes, let's take a look at RawUI.

That looks the most promising:

$host.UI.RawUI | get-member


   TypeName: System.Management.Automation.Internal.Host.InternalHostRawUserInterface

Name                  MemberType Definition
----                  ---------- ----------
Equals                Method     bool Equals(System.Object obj)
FlushInputBuffer      Method     void FlushInputBuffer()
GetBufferContents     Method     System.Management.Automation.Host.BufferCell[,] GetBufferContents(System.Management.Automation.Host.Rectangle r)
GetHashCode           Method     int GetHashCode()
GetType               Method     type GetType()
LengthInBufferCells   Method     int LengthInBufferCells(string str), int LengthInBufferCells(string str, int offset), int LengthInBufferCells(cha...
NewBufferCellArray    Method     System.Management.Automation.Host.BufferCell[,] NewBufferCellArray(string[] contents, System.ConsoleColor foregro...
ReadKey               Method     System.Management.Automation.Host.KeyInfo ReadKey(System.Management.Automation.Host.ReadKeyOptions options), Syst...
ScrollBufferContents  Method     void ScrollBufferContents(System.Management.Automation.Host.Rectangle source, System.Management.Automation.Host.C...
SetBufferContents     Method     void SetBufferContents(System.Management.Automation.Host.Coordinates origin, System.Management.Automation.Host.Bu...
ToString              Method     string ToString()
BackgroundColor       Property   System.ConsoleColor BackgroundColor {get;set;}
BufferSize            Property   System.Management.Automation.Host.Size BufferSize {get;set;}
CursorPosition        Property   System.Management.Automation.Host.Coordinates CursorPosition {get;set;}
CursorSize            Property   int CursorSize {get;set;}
ForegroundColor       Property   System.ConsoleColor ForegroundColor {get;set;}
KeyAvailable          Property   bool KeyAvailable {get;}
MaxPhysicalWindowSize Property   System.Management.Automation.Host.Size MaxPhysicalWindowSize {get;}
MaxWindowSize         Property   System.Management.Automation.Host.Size MaxWindowSize {get;}
WindowPosition        Property   System.Management.Automation.Host.Coordinates WindowPosition {get;set;}
WindowSize            Property   System.Management.Automation.Host.Size WindowSize {get;set;}
WindowTitle           Property   string WindowTitle {get;set;}
  

BINGO! I see BufferSize and WindowSize, and I know from the GUI Properties page that those are the relevant settings, but just to verify:

$host.UI.RawUI.BufferSize | get-member


   TypeName: System.Management.Automation.Host.Size

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()
Height      Property   int Height {get;set;}
Width       Property   int Width {get;set;}


$host.UI.RawUI.WindowSize | get-member


   TypeName: System.Management.Automation.Host.Size

Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
ToString    Method     string ToString()
Height      Property   int Height {get;set;}
Width       Property   int Width {get;set;}
  

And there we have it.  Both of them can be retrieved and set.  So, I came up with a little script, Set-myConSize, that lets me restore the window to its default size, or set it to a new size if I'm doing something that needs a bit of window size tweaking.

<#
.Synopsis
Resets the size of the current console window
.Description
Set-myConSize resets the size of the current console window. By default, it
sets the windows to a height of 40 lines, with a 3000 line buffer, and sets the 
the width and width buffer to 120 characters. 
.Example
Set-myConSize
Restores the console window to 120x40
.Example
Set-myConSize -Height 30 -Width 180
Changes the current console to a height of 30 lines and a width of 180 characters. 
.Parameter Height
The number of lines to which to set the current console. The default is 40 lines. 
.Parameter Width
The number of characters to which to set the current console. Default is 120. Also sets the buffer to the same value
.Inputs
[int]
[int]
.Notes
    Author: Charlie Russel
 Copyright: 2017 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 28 April, 2017 (cpr)
   ModHist:
          :
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$False,Position=0)]
     [int]
     $Height = 40,
     [Parameter(Mandatory=$False,Position=1)]
     [int]
     $Width = 120
     )
$Console = $host.ui.rawui
$Buffer  = $Console.BufferSize
$ConSize = $Console.WindowSize

# If the Buffer is wider than the new console setting, first reduce the buffer, then do the resize
If ($Buffer.Width -gt $Width ) {
   $ConSize.Width = $Width
   $Console.WindowSize = $ConSize
}
$Buffer.Width = $Width
$ConSize.Width = $Width
$Buffer.Height = 3000
$Console.BufferSize = $Buffer
$ConSize = $Console.WindowSize
$ConSize.Width = $Width
$ConSize.Height = $Height
$Console.WindowSize = $ConSize
  

One quick comment on this script -- you can't set the BufferSize to smaller than the current WindowSize. With a Height buffer set to 3,000, that's not likely to be a problem, but if you don't want scroll bars on the bottom of your console windows (and you do NOT, trust me!), then you need the console WindowSize.Width to be the same as the BufferSize.Width. So if your reducing, you need to change the WindowSize first, then you can reduce the BufferSize. If you're increasing width, you need to do the buffer first.

 

Finally, I set an alias in my $Profile:

Set-Alias Resize -Value Set-myConSize

 

Configuring Windows Server 2016 Core with and for PowerShell

I know I owe you more on creating a lab with PowerShell, and I'll get to that in a few days. But having just set up a new domain controller running Server 2016 Core, I thought I'd include a couple of tricks I worked through to make your life a little easier if you choose to go with core.

First: Display Resolution -- the default window for a virtual machine connected with a Basic Session in VMConnect is 1024x768. Which is just too small. So, we need to change that. Now in the full Desktop Experience, you'd right click and select Display Resolution, but that won't work in Server Core, obviously. Instead we have PowerShell. Of course. The command to set the display resolution to 1600x900 is:

Set-DisplayResolution -Width 1600 -Height 900

This will accept a -Force parameter if you don't like being prompted. A key point, however, is that it ONLY accepts supported resolutions. For a Hyper-V VM, that means one of the following resolutions:

1920x1080     1600x1050     1600x1200
1600x900      1440x900      1366x768
1280x1024     1280x800      1280x720
1152x864      1024x768       800x600

Now that we have a large enough window to get something done, start PowerShell with the Start PowerShell (that space is on purpose, remember we're still in a cmd window.)  But don't worry, we'll get rid of that cmd window shortly.

Now that we have a PowerShell window, you can set various properties of that window by using any of the tricks I've shown before, such as Starting PowerShell Automatically which sets the Run key to start PowerShell for the current user on Login with:

 New-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\Run `
                       -Name  "Windows PowerShell" `
                       -Value "C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe"

 

I also showed you how to set the PowerShell window size, font, etc in Starting PowerShell Automatically Revisited. And, of course, you can set the PowerShell window colour and syntax highlighting colours as described in Setting Console Colours. Of course, all my $Profile tricks work as well, so check those out.

 

So, now that we've configured the basics of our PowerShell windows, let's set PowerShell to replace cmd as the default console window. To do that, use the Set-ItemProperty cmdlet to change the WinLogon registry key:

Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' `
                 -Name Shell `
                 -Value 'PowerShell.exe -NoExit'

Viola! Now, when we log on to our Server Core machine, it will automatically open a pair of PowerShell windows, one from WinLogon registry key and one from the Run registry key.

PowerShell: Get-Credential from a file

If you routinely have to log into a separate domain, it can be a nuisance to always have to run Get-Credential. Plus writing scripts with a -Credential parameter is a nuisance because if you call Get-Credential in the script, it will always prompt you.

 

I run a separate lab network here, with an Active Directory domain of TreyResearch.net. I got tired of always having scripts prompt me for credentials, or even more annoying, have routine PowerShell commands against computers in the lab fail because I didn't have credentials for that domain. The answer is pretty simple -- first, I stored my password securely in a file with:

Read-Host -AsSecureString `
         | ConvertFrom-SecureString `
         | Out-File $Home\Documents\WindowsPowerShell\TCred.txt

Now, I can use that password to create a PSCredential object that I can pass into a script.

$tPW = Get-Content $home\Documents\WindowsPowerShell\TCred.txt `
         | ConvertTo-SecureString
$tCred = New-Object -TypeName System.Management.Automation.PSCredential `
                    -ArgumentList "TreyResearch\Charlie",$tPW

 

Because of the way SecureString works, and how Windows encrypts and decrypts objects, this password can only be read from the account that created it. Now, if I want to add a -Credential parameter to my scripts, I use the following:

[CmdletBinding()]
Param([Parameter(Mandatory=$false,ValueFromPipeLine=$True)]
      [PSCredential]
      $Credential = $NULL
     )

if ( $Credential ) {
   $tCred = $Credential
} else {
   $tPW = Get-Content $home\Documents\WindowsPowerShell\TCred.txt `
        | ConvertTo-SecureString 
   $tCred = New-Object -TypeName System.Management.Automation.PSCredential `
                       -ArgumentList "TreyResearch\Charlie",$tPW
}

ETA: if you create a script for this, you'll need to "dot source" the script to add the credential to your environment, but you can use that script in the pipeline to insert a credential into the pipeline. Or, add the relevant lines into your $Profile, and the credential is then available in your environment.

A More Readable Path

The path on many modern computers gets to be so long that reading it is a nuisance. Worse, from within Windows PowerShell, there isn’t a built in equivalent to the old cmd “path” command. Yet all the information we need is there, it’s just not where we might expect it. The actual executable path for a PowerShell session is stored in $ENV:path. Here’s a quick function you put in your $profile to print out the PATH, one line for each item:

Function path { 
   $p=$env:path 
   $p.Split(':') 
}

Simple, wasn't it? Now, when you type "path" from the Windows PowerShell prompt, you'll see something like this:

PSH> path 
C:\Windows\system32 
C:\Windows 
C:\Windows\System32\Wbem 
C:\Windows\System32\WindowsPowerShell\v1.0\ 
C:\Users\charlie.russel\psbin

Mapping Drives

My standard environment expects to have several drive mappings wherever I’m logged in to my network. Even when I’m running on a computer that isn’t joined to the domain. To facilitate that, I have a simple “mapdrives.cmd” file that has the necessary net use commands in it to map them.  Then my default PowerShell $profile calls mapdrives.cmd. This works well, except that the result is fairly messy if there’s a problem.

Problem? Sure. For example, if my laptop isn’t in the office, then it won’t be able to map the drives on my office network. Or, if the dries are already mapped as part of a group policy, I’ll get an error when I try to map them. So, I decided to get smarter about it. Here’s the relevant section of my $profile:

$InOffice = test-connection -quiet 192.168.50.3
$isMapped = Get-WMIObject -query "Select * from Win32_LogicalDisk where DeviceID='A:'"
if ($InOffice -and ! $isMapped ){
$maps='C:\Windows\system32\mapdrives.cmd'
if ($maps) {& $maps }
}

Now there may be even better ways to do this, but for me this works well. The $InOffice test checks for the presence of a server that should be reachable if I’m in the office, but that will not be reachable anywhere else. The $isMapped test checks to see if one of my standard drive mappings has already been done.

The result? If I’m not in the office, it won’t bother mapping drives and thus save some significant startup time while it tried to map them and then failed. And if I’m in the office, but they’ve already been mapped, it won’t bother either, saving no time but keeping my PowerShell window from echoing all those errors as it tries to map something that is already mapped.

Find this useful? Got a better way to do it? Leave a comment, please.

Charlie.

 

ETA: Yech, boy was this ugly back then. But now we have many new features in PowerShell v5, so I've updated things a bit. See my Mapping Drives Revisited post for a much better way to do this.

PowerShell ISE and ASCII Files

If any of you have ever created a file in the PowerShell ISE and then wondered why it doesn't look right when you open it in some other editor, or try to run it, you've just run into a "feature" that I wish were not the default--New files are saved as Unicode files. There's no problem with you're working with an existing ASCII file, the ISE will preserve the format. But if you're creating a new file, it saves as Unicode. Now this has always annoyed me, but I never really did anything about it, just worked around it when I got hit with it. But the other day I came across an old blog post from Doug Finke, a fellow PowerShell MVP. He adds the following line to his ISE $profile:

$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Save File ASCII",{$psISE.CurrentFile.Save([Text.Encoding]::ASCII)}, $null) | out-null

That's it. One line of code, and there's a new menu item on the AddOns Menu to solve the problem. Sweet.

(For those who haven't created an ISE $profile yet, it goes in the same directory as your regular PowerShell $profile, but has the filename: Microsoft.PowerShellISE_profile.ps1. )

Charlie.