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: = 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: = 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.

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.