Keith Hill posted a bit of PowerShell code a few days ago that piqued my interest. I knew that in PowerShell, the $profile variable would list your current profile.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
PS C:\> $profile
C:\Users\Jeff\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
This is handy because it makes it easy to call up my script editor if I need to make a change. But Keith showed that there’s much more useful information there and I decided to take advantage.
As you probably know there are several profiles that PowerShell looks for.
- All Users for All Hosts
- All Users for the Current Host
- Current User for All Hosts
- Current User for the Current Host
The automatic $profile variable exposes all of these profile values, which is great since I can never remember where they are supposed to live. The best way to view them is to pipe $profile to Select-Object:
I ignore the length property. By the way, the ISE (integrated script environment) has separate profiles.
How is any of this helpful? First off we can use this information to see which of these profiles already exist. I’ll stick to the PowerShell console for my examples, but this should work for the ISE as well.
As you can see from the screen shots, each profile is a property of $profile.
PS C:\> $profile.AllUsersAllHosts
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1
We can use Test-Path to see if this file exists.
PS C:\> Test-path $profile.AllUsersAllHosts
False
Obviously what would be useful would be to check all the properties.
$profile | get-member -member noteproperty | foreach {
$file=$profile.($_.name)
if (test-path $file) {
write-host $file -foregroundcolor Green
}
else {
write-host $file -foregroundcolor Red
}
} #end foreach
The profile name is stored as a NoteProperty which is then piped to ForEach-Object which gets the corresponding profile value and then uses Test-Path to see which files exist. Those that do are displayed in green.
But let’s take this a few steps further. Because these profiles run automatically, it is theoretical attack vector. Ideally you should create all of these scripts, digitally sign and and use the AllSigned execution policy. If you start PowerShell and it complains that it can’t run a script because of a broken signature, that’s your clue that something or someone modified one of your profiles. But because they don’t have access to your code signing certificate, they could not resign it. Signed profiles are simply another layer of security that you should be employing.
Given all of that, I’ll revise this code to create missing profiles. The result is my Add-Profile.ps1 script.
#Requires -version 2.0
$profile | get-member -member noteproperty | foreach {
$file=$profile.($_.name)
if (test-path $file) {
write-host "OK: $file" -foregroundcolor Green
}
else {
write-host "Creating: $file" -foregroundcolor Red
#split off the path and verify it exists. If not,
#create it
$parent=Split-Path($file)
if (! (test-path $parent)) {mkdir $parent | out-Null}
#Create the profile script
$content="Created {0} by {1}\{2} and left intentionally empty." -f (get-date),$env:userdomain,$env:username
Set-Content -path $file -value $content -PassThru
#insert your code here to digitally sign the script
}
} #end foreach
If the profile script doesn’t exist I’ll create it using Set-Content and inserting a line of text indicating when the file was created and that it is intentionally left blank. However, the cmdlet will fail if the parent folder doesn’t exist, which it might not for the current user, current host profile. Using Split-Path I can get the path and create it. The cmdlet will create all the necessary subfolders as well.
The only thing my code doesn’t do is sign the profile but I hope you will add those necessary pieces.