PowerShell $PROFILE, Variables and NoteProperty / Add-Member - powershell

$profile is causing me some headaches. $Profile.GetType() resolves to String which is fine, but it has NoteProperty values:
$profile | Get-Member -Type NoteProperty
AllUsersAllHosts, AllUsersCurrentHost, CurrentUserAllHosts, CurrentUserCurrentHost
When I type $profile, the CurrentUserCurrentHost NoteProperty is returned. This is fine, but I need to change this value - it's complicated, but my corporate VPN uses a network profile and when I am online it tries to reference that location for my $profile meaning that every console startup takes 9 seconds (horribly slow). I can get that down to 1 sec if the profile is loaded locally, but that means changing these values. To do this, I put the following into the AllUsersAllHosts profile.ps1
$profile = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
That's fine, but by doing this, I find that all of the NoteProperty values are deleted! So I tried:
$profile.CurrentUserCurrentHost = C:\Users\($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"
But this fails, as the root value of $Profile continues to point at the network profile and Console startup is 9 seconds again!
I then also noticed the following weirdness:
$x = [string]$profile
$x -eq $profile
Ny main questions are:
• Why does $x return True even though $x has none of the NoteProperty values in $profile (as the objects are definitely not the same!)?
• How do I control what the root value of $profile is without destroying the NoteProperty values, and
• How can I update .CurrentUserAllHosts and .CurrentUserCurrentHost in such a way that the root value will also update accordingly? i.e. Even if I do the below, the root value of $profile remains unchanged (still points at the very slow network profile location):
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersAllHosts" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "AllUsersCurrentHost" -NotePropertyValue "C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserAllHosts" -NotePropertyValue "C:\Users\$($env:Username)\Documents\WindowsPowerShell\profile.ps1"
Add-Member -InputObject $PROFILE -NotePropertyName "CurrentUserCurrentHost" -NotePropertyValue "C:\Users\$($env:Username)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1"

While it is technically possible to copy NoteProperty members from one string instance to another (see below), that won't help you, unfortunately:
PowerShell exposes the $PROFILE variable for the user's benefit:
It doesn't use that variable's values internally to determine the profile locations when it loads profiles,
these profile locations are not configurable.
You should treat $PROFILE as a read-only variable, even though it can technically be modified.
For workarounds, see the answer to this related question.
If starting interactive sessions is the focus, the most promising approach is to create a dedicated shortcut file (*.lnk) for launching your PowerShell sessions, defined with a target command such as the following:
powershell -noprofile -noexit -c ". c:\CustomProfileDir\profile.ps1"
To use PowerShell [Core] 6+, substitute pwsh for powershell.
Copying NoteProperty members between objects:
Note:
As stated, this will not solve your problem, but it illustrates how NoteProperty members can be copied from one object to another, even between strings that have different values.
With [string] instances specifically, using -PassThru and assigning back to the input variable ($obj = $obj | Add-Member -PassThru ...) is required in order for the NoteProperty members to be retained; for other types, $obj | Add-Member ... is sufficient.
# Assign the new string value to a new (temporary) variable.
$newProfile = '/path/to/my.ps1'
# Copy the NoteProperty members from the original, decorated string
# to the new variable's string instance.
$PROFILE.psobject.properties | where MemberType -eq 'NoteProperty' | foreach {
$newProfile = $newProfile |
Add-Member -PassThru -NotePropertyName $_.Name -NotePropertyValue $_.Value
}
# You can now assign the new variable to the old one,
# but note that *with $PROFILE that won't help*.
# $PROFILE SHOULD BE TREATED AS READ-ONLY.
$PROFILE = $newProfile

As #mklement0 pointed out, I cannot bypass the functionality of $profile, but I can trick it. Without the below, all shells and scripts take 6.5 to 9 sec to start when I am remotely connected to my corporate VPN (part of that, about 3 seconds is my $profile functions etc). With the below, all consoles and scripts startup in 1 second (and that includes my $profile functions about 1000 lines of code). Putting this solution here in case of help to others.
Running the code at the bottom of this answer results in pushing the following into my $Profile.AllUsersAllHosts which will now always reference a profile at the default C:\ location (and correctly per host type as well - console, ISE, VSCode etc).
Set-Location C:\Users\$($env:Username)
# Prevent loading profile from network share
if ($Profile.CurrentUserCurrentHost -ne "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])") {
$Profile = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])"
if (Test-Path "$Profile") { . $Profile }
}
All I then need to do is delete any profiles on the network share and now I get 1 second load times whether connected remotely to the VPN or not, and whether I open a console or a script etc.
Write-Host ""
Write-Host "Do you want to update `$Profile.AllUsersAllHosts to always redirect to C:\Users\$($env:Username) ?"
Write-Host "This can significantly speed up PowerShell load times when working with a network share over a VPN."
Write-Host "Note that this is just a bypass to always use the *default* profile location on C:\."
Write-Host " `$Profile.AllUsersAllHosts = C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
Write-Host " `$Profile.CurrentHostCurrentUser = C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split("\"))[-1]_profile.ps1"
Write-Host ""
$ProfileAdmin = "C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1"
# Use (($Profile).split("\"))[-1] to capture correct prefix PowerShell, VSCode. $ShellId does not work for VSCode etc
$ProfileUser = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split('\')[-1])"
$ProfileUserText = '"C:\Users\$($env:Username)\Documents\WindowsPowerShell\$(($Profile).split(`"\`")[-1])"'
Write-Host "Press any key to use `$Profile.AllUsersAllHosts to stop using a profile from a network share"
pause
# - Create $Profile.AllUsersAllHosts
# - Set-Location C:\Users\$($env:Username) (override Admin defaulting into system32)
# - If the system decides that it will use C:\ have to check that so that we do not double-load $Profile!
# - If that condition is ok, set $Profile = "C:\Users\$($env:Username)\Documents\WindowsPowerShell\$($ShellId)_profile.ps1"
# - Then just dotsource $Profile as it's set to the C:\ drive as required.
if (!(Test-Path $(Split-Path $ProfileAdmin))) { mkdir $(Split-Path $ProfileAdmin) -Force }
if (!(Test-Path $(Split-Path $ProfileUser))) { mkdir $(Split-Path $ProfileUser) -Force }
Write-Host "`nCreating backup of existing profile ..."
If (Test-Path $ProfileAdmin) { Copy-Item -Path "$($ProfileAdmin)" -Destination "$($ProfileAdmin)_$(Get-Date -format "yyyy-MM-dd__HH-mm-ss").txt" }
Set-Content -Path $ProfileAdmin -Value "Set-Location C:\Users\`$(`$env:Username)"
Add-Content -Path $ProfileAdmin -Value ""
Add-Content -Path $ProfileAdmin -Value "# Prevent loading profile from network share"
Add-Content -Path $ProfileAdmin -Value "if (`$Profile.CurrentUserCurrentHost -ne $ProfileUserText) {"
Add-Content -Path $ProfileAdmin -Value " `$Profile = $ProfileUserText"
Add-Content -Path $ProfileAdmin -Value " if (Test-Path `"`$Profile`") { . `$Profile }"
Add-Content -Path $ProfileAdmin -Value "}"

Related

Powershell error writing writing to all users' registry

I'm attempting to add a wallpaper, along with certain parameters, to each user on a computer. It's been hit and miss with this working/not working on computers. The ones that fail I get the error "Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'."
The variables $WallpaperPath and $Style are coming from another source within Automation Manager (using N-Central).
# Get each user profile SID and Path to the profile
$UserProfiles = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*" | Where {$_.PSChildName -match "S-1-5-21-(\d+-?){4}$" } | Select-Object #{Name="SID"; Expression={$_.PSChildName}}, #{Name="UserHive";Expression={"$($_.ProfileImagePath)\NTuser.dat"}}
# Add in the .DEFAULT User Profile
$DefaultProfile = "" | Select-Object SID, UserHive
$DefaultProfile.SID = ".DEFAULT"
$DefaultProfile.Userhive = "C:\Users\Public\NTuser.dat"
$UserProfiles += $DefaultProfile
# Loop through each profile on the machine</p>
Foreach ($UserProfile in $UserProfiles) {
    # Load User ntuser.dat if it's not already loaded
    If (($ProfileWasLoaded = Test-Path Registry::HKEY_USERS\$($UserProfile.SID)) -eq $false) {
        Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE LOAD HKU\$($UserProfile.SID) $($UserProfile.UserHive)" -Wait -WindowStyle Hidden
    }
# Write to the registry
$key = "Registry::HKEY_USERS\$($UserProfile.SID)\Control Panel\Desktop"
Set-ItemProperty -Path $key -name Wallpaper -value "$WallpaperPath"
Set-ItemProperty -Path $key -name TileWallpaper -value "0"
Set-ItemProperty -Path $key -name WallpaperStyle -value "$Style" -Force
# Unload NTuser.dat
If ($ProfileWasLoaded -eq $false) {
    [gc]::Collect()
    Start-Sleep 1
    Start-Process -FilePath "CMD.EXE" -ArgumentList "/C REG.EXE UNLOAD HKU\$($UserProfile.SID)" -Wait -WindowStyle Hidden| Out-Null
}
}
I'm looking for this to load a temporary HKU hive for each user that's not currently logged in, and has an NTuser.dat file, and write the registry entries specified. It should then unload any hive for users it added.
Instead of $UserProfiles = ..., use either [array] $UserProfiles = ... or $UserProfiles = #(...) in order to ensure that $UserProfiles always contains an array, even if the command happens to return just one object.
That way, your += operation is guaranteed to work as intended, namely to (loosely speaking) append an element to the array.[1]
Note that PowerShell's pipeline has no concept of an array, just a stream of objects. When such a stream is collected, a single object is captured as itself; only two or more objects are captured in an array ([object[]]) - see this answer for more information.
A simple demonstration:
2, 1 | ForEach-Object {
$result = Get-ChildItem / | Select-Object Name -First $_
try {
$result += [pscustomobject] #{ Name = 'another name' }
"`$result has $($result.Count) elements."
} catch {
Write-Warning "+= operation failed: $_"
}
}
In the first iteration, 2 objects are returned, which are
stored in an array. += is then used to "append" another element.
In the second iteration, only 1 object is returned and stored as such.
Since [pscustomobject], which is the type of object returned by Select-Object, doesn't define a + operation (which would have
to be implemented via an op_Addition() method at the .NET level), the error you saw occurs.
Using an [array] type constraint or #(...), the array-subexpression operator operator, avoids this problem:
2, 1 | ForEach-Object {
# Note the use of #(...)
# Alternatively:
# [array] $result = Get-ChildItem \ | Select-Object Name -First $_
$result = #(Get-ChildItem / | Select-Object Name -First $_)
$result += [pscustomobject] #{ Name = 'another name' }
"`$result has $($result.Count) elements."
}
As noted, [array] $results = Get-ChildItem \ | Select-Object Name -First $_ works too, though there are subtle differences between the two approaches - see this answer.
As an aside:
To synchronously execute console applications or batch files and capture their output, call them directly (c:\path\to\some.exe ... or & $exePath ...), do not use Start-Process (or the System.Diagnostics.Process API it is based on) - see this answer. GitHub docs issue #6239 provides guidance on when use of Start-Process is and isn't appropriate.
That is, you can just make calls such as the following:
REG.EXE LOAD "HKU\$($UserProfile.SID)" "$($UserProfile.UserHive)"
Also, it's easier and more efficient to construct [pscustomobject] instances with their literal syntax (v3+; see the conceptual about_PSCustomObject help topic):
$UserProfiles += [pscustomobject] #{
SID = ".DEFAULT"
Userhive = "C:\Users\Public\NTuser.dat"
}
[1] Technically, a new array must be created behind the scenes, given that arrays are fixed-size data structures. While += is convenient, it is therefore inefficient, which matters in loops - see this answer.

How can you replace multiple UNC paths of mapped drives via registry using PowerShell?

Our servers team has implemented a DFS, but users across the company still have drives mapped using the server name(s) at various sites. I'd like to push out a PS script that updates a SINGLE registry value (per drive).
My goal is to look through each drive letter key, if the key exists and the remote path starts with the server name then replace it with, the DFS name \\domain.com\SITE\+remainder of the path. This way users keep the same drive letters without having to "remap" their drives
Using Denver office as an example...
$OldServer = "\\denvernas01\"
$NewServer = "\\domain.com\DEN\"
$DriveLetterArray = "A","B","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
foreach ($DriveLetter in $DriveLetterArray)
{ $Drives = Get-ItemPropertyValue HKCU:\Network\$DriveLetter -Name RemotePath -ErrorAction SilentlyContinue
$RemainingPath = $Drives.Replace($OldServer,"")
foreach ($Drive in $Drives)
{ if ($Drive -like "*$OldServer*")
{ Set-ItemProperty HKCU:\Network\$DriveLetter -Name RemotePath -Value "$NewServer"+"$RemainingPath" }}}
EDIT
^^^This currently works, but only if the server name in RemotePath is all lower case. I.e. the server variables are case sensitive. Any thoughts on how to define the $OldServer & $NewServer variables so it will work with case variations???? e.g. Denvernas01, DENVERNAS01 (or anything inbetween)
I've come across a few threads discussing New-PSDrive, Get-WMIObject, etc, but I'd really like to just replace this one registry value. This would be a good "patch" that would take some stress off of our desktop support team. Trust me - I'll be advocating for GPO to push out common mapped drives once this is all over.
Any feedback is greatly appreciated. Thank you!
If anyone out there is interested, this is what I ended up with, and it worked like a charm. Thought I'd share...
$OldServer = '\\denvernas01'
$NewServer = '\\Domain.com\DEN'
$DriveLetterArray = "A","B","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"
foreach ($DriveLetter in $DriveLetterArray){
$DrivePath = $null; $ConvertedPath = $null
$DrivePath = Get-ItemPropertyValue -Path "HKCU:\Network\$DriveLetter" -Name "RemotePath" -ErrorAction SilentlyContinue
if ($DrivePath -eq $null) {continue}
#Replace Old Drive Path
if ($DrivePath -like "*${OldServer}*") {
$ConvertedPath = $DrivePath -ireplace [regex]::Escape("$Oldserver"), $NewServer
$ConvertedPath
Set-ItemProperty -Path "HKCU:\Network\$DriveLetter" -Name "RemotePath" -Value "${ConvertedPath}"
} else {
#Write-Host "no match"
continue
}
#Write-Host ""
}
#Remove previous mountpoints
Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\* | Where-Object Name -Match "##denvernas" | Remove-Item

Azure DevOps Azure PowerShell task output variable

I'm creating a release pipeline using one Azure PowerShell Task and PowerShell task. In the the Azure Powershell Task, I have the following code
$groupInfos = #()
for ([int]$i = 0; $i -lt $azureADGroupsObj.Count)
{
$groupInfo = New-Object PSObject
$groupInfo | Add-Member -MemberType NoteProperty -Name "displayName" -Value $azureADGroupsObj[$i].DisplayName
$groupInfo | Add-Member -MemberType NoteProperty -Name "Id" -Value
$azureADGroupsObj[$i].Id
$groupInfos += $groupInfo
$i++
}
return $groupInfos
Write-Host "##vso[task.setvariable variable=azureADGroups;]$groupInfos"
I am trying to store $groupInfos into azureADGroups variable here.
but when I run a PowerShell task in the next step under same job, it says the term "azureADGroup" is not recognized.. seems like the variable wasn't set..does anyone know what am I missing here?
I found 3 problems in your script:
You do not need to set the reference name.
There is a return before the write variable command. So, the write variable command will not be executed.
The write variable command can only use single-line string. However, the $groupInfos is an object. It will not be implicitly converted to a string. You need to use "ConvertTo-Json -Compress" command to convert it to a string.
I tested at my pipeline:
$groupInfosString = $groupInfos | ConvertTo-Json -Compress
write-host $groupInfos
write-host $groupInfosString
Write-Host "##vso[task.setvariable variable=azureADGroups;]$groupInfos"
Write-Host "##vso[task.setvariable variable=azureADGroupsFromString;]$groupInfosString "
From the debug log, we can check that variable "azureADGroupsFromString" is successfully set.
Update:
You can use the following script in next PS task:
$objs = '$(azureADGroupsFromString)' | ConvertFrom-Json
foreach( $obj in $objs){
Write-Host ("displayName:{0} Id:{1}" -f $obj.displayName, $obj.Id)
}
Output:
Update:
If you want to pass it to next PS task via arguments, please enclose the variable in single quotes. In this way, it will be considered as a string.

Change "Windows font size (DPI)" in PowerShell?

I'm using a laptop at office (Windows 7) with a station and double screen and at home without station.
The point is I have to change text size each time I switch from station to standlone laptop, because the text size is too big on my double screen, but too small on my laptop screen.
To proceed:
I right-click on desk screen, choose change resolution then "get text and other elements bigger or smaller" to choose 100%, 125%, etc...
I need to restart my session to get the settings applied.
(Note: I'm using a French system, and texts are not exactly the same on us version I suppose).
It's not very convenient so I'd like to automate this, perhaps with a PowerShell script.
Ideally the script may detect if I'm using laptop alone or station with its two screens). Plus, without session restart (I doubt this last point is feasible).
How do I get started? If this is possible.
As supposed in the other answers, the setting under HKLM is not the correct place as the dpi scaling is a user defined setting. The correct registry key is HKCU:\Control Panel\Desktop with the value LogPixels.
More information about all DPI-related registry settings can be found in DPI-related APIs and registry settings.
I wrote a tiny PowerShell script that changes the DPI scaling depending on the current scaling and performs the user logoff, so I just have to execute the script when I put my device to a different monitor.
cd 'HKCU:\Control Panel\Desktop'
$val = Get-ItemProperty -Path . -Name "LogPixels"
if($val.LogPixels -ne 96)
{
Write-Host 'Change to 100% / 96 dpi'
Set-ItemProperty -Path . -Name LogPixels -Value 96
} else {
Write-Host 'Change to 150% / 144 dpi'
Set-ItemProperty -Path . -Name LogPixels -Value 144
}
logoff;exit
Apparently you can set the LogPixels property of
HKLM:/Software/Microsoft/Windows NT/CurrentVersion/FontDPI
which is reiterated in a lot of places around the net. However, I got the impression that dpi was a user setting which makes no sense to have under HKLM.
This is the most straightforward method I've found. I lightly modified the provided function so it's an easy copy/paste. It does not require any registry calls or anything. Simple and very effective.
You can call it like this
Set-Scaling -scaling 0
function Set-Scaling {
# Posted by IanXue-MSFT on
# https://learn.microsoft.com/en-us/answers/questions/197944/batch-file-or-tool-like-powertoy-to-change-the-res.html
# $scaling = 0 : 100% (default)
# $scaling = 1 : 125%
# $scaling = 2 : 150%
# $scaling = 3 : 175%
param($scaling)
$source = #'
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(
uint uiAction,
uint uiParam,
uint pvParam,
uint fWinIni);
'#
$apicall = Add-Type -MemberDefinition $source -Name WinAPICall -Namespace SystemParamInfo –PassThru
$apicall::SystemParametersInfo(0x009F, $scaling, $null, 1) | Out-Null
}
Sorry, I misread the question. I thought you wanted to control the PowerShell windows.
As already mentioned you could set the LogPixels setting in the registry, to see what the current setting is, try this:
Get-Item -Path Registry::'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI' | Select-Object -ExpandProperty Property
If the LogPixels key is there it will show, you can create it if it does not exist:
Set-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI\LogPixels'
NB: You have to run this with privileges that allow you to manipulate the registry.
There is a good introduction to this over at TechNet.
#Torben Schramme I found that I had to add one more ItemProperty Win8DpiScaling for this work. But, I don't find the "logoff; exit" function working - I still have to do it manually.
cd 'HKCU:\Control Panel\Desktop'
$val = Get-ItemProperty -Path . -Name "LogPixels"
if($val.LogPixels -ne 96)
{
Write-Host 'Change to 100% / 96 dpi'
Set-ItemProperty -Path . -Name LogPixels -Value 96
Set-ItemProperty -Path . -Name Win8DpiScaling 0
} else {
Write-Host 'Change to 150% / 144 dpi'
Set-ItemProperty -Path . -Name LogPixels -Value 144
Set-ItemProperty -Path . -Name Win8DpiScaling 1
}
logoff;exit
After much time, I can't find anything in google.
Well, I made my own script:
$perfis = (Get-ChildItem Registry::HKEY_USERS\ | Where-Object {$_.Name -match "S-1"} | ForEach-Object {Get-ItemProperty "Registry::$_\Control Panel\Desktop" -Name "Win8DpiScaling" -ErrorAction SilentlyContinue}).PSPath
foreach ($_ in $perfis) {Set-ItemProperty -Path "Registry::$_" -Name "Win8DpiScaling" -Value 0}
$monitores = (Get-ChildItem Registry::HKEY_USERS\ | Where-Object {$_.Name -match "S-1"} | ForEach-Object {Get-ChildItem "Registry::$_\Control Panel\Desktop\PerMonitorSettings" -ErrorAction SilentlyContinue}).PSPath
foreach ($_ in $monitores) {Set-ItemProperty -Path "Registry::$_" -Name "DpiValue" -Value 0}
By comparing the output of Get-ItemProperty -Path 'HKCU:\Control Panel\Desktop' before and after using the Windows GUI to set the scaling level, I found the following properties needed setting; worked for me:
cd 'HKCU:\Control Panel\Desktop'
Set-ItemProperty -Path . -Name LogPixels -Value 144
Set-ItemProperty -Path . -Name Win8DpiScaling -Value 1
Set-ItemProperty -Path . -Name FocusBorderHeight -Value 2
Set-ItemProperty -Path . -Name FocusBorderWidth -Value 2
Write-Host 'Sign out and sign back in again to see changes.'
Love this Answer, anyone have any idea on how to set for a single display instead of all at the same time? I have 3 monitors, and this is a universal change.
Set-Scaling -scaling 0
function Set-Scaling {
# Posted by IanXue-MSFT on
# https://learn.microsoft.com/en-us/answers/questions/197944/batch-file-or-tool-like-powertoy-to-change-the-res.html
# $scaling = 0 : 100% (default)
# $scaling = 1 : 125%
# $scaling = 2 : 150%
# $scaling = 3 : 175%
param($scaling)
$source = #'
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(
uint uiAction,
uint uiParam,
uint pvParam,
uint fWinIni);
'#
$apicall = Add-Type -MemberDefinition $source -Name WinAPICall -Namespace SystemParamInfo –PassThru
$apicall::SystemParametersInfo(0x009F, $scaling, $null, 1) | Out-Null
}
These simple steps worked for me:
Download Win7AndW2K8R2-KB3191566-x64.ZIP from https://learn.microsoft.com/en-us/powershell/wmf/5.1/install-configure
Unzip the file in tmp folder
Open a Powershell command window as Administrator, go in tmp folder where file has been unzipped and execute the following commands :
set-executionpolicy remotesigned
.\Install-WMF5.1.ps1

How to set a binary registry value (REG_BINARY) with PowerShell?

How to set a binary registry value (REG_BINARY) with PowerShell?
Background:
I need to change some properties of the ASP.NET State service using a PowerShell script. Unfortunately, the built-in PowerShell cmdlet Set-Service only lets you modify the service description, startup type, display name, and status. I need to modify the Subsequent failures property found on the Recovery tab (when viewing the service's properties). I found that this value was stored in the registry as a REG_BINARY value.
An export of the value looks like this:
[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\aspnet_state]
"FailureActions"=hex:50,33,01,00,00,00,00,00,00,00,00,00,03,00,00,00,0e,00,00,\
00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00
In Powershell there is a Set-ItemProperty cmdlet with which you can set registry value values. For a string or dword value, you can just pass a string or an int. I know which hex value in the array to change, but I can't figure out how to set a binary value.
The following line gives you an example how to create one
New-ItemProperty -Path . -Name Test -PropertyType Binary -Value ([byte[]](0x30,0x31,0xFF))
and how to change an existing one:
Set-ItemProperty -Path . -Name Test -Value ([byte[]](0x33,0x32,0xFF))
Is it just me who feels this misses the main part of this question?
How would you go about changing the original:
50,33,01,00,00,00,00,00,00,00,00,00,03,00,00,00,0e,00,00,\
00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00
Into a format like:
([byte[]](0x33,0x32,0xFF))
EDIT: After trying to get this working it turns out you just prefix all of the pairs with '0x'. Not sure why that was not mentioned in the answer. So just change the above to:
0x50,0x33,0x01,0x00,0x00,0x00,0x00,0x00... etc.
Then wrap that in the following:
([byte[]](0x50,0x33,0x01,0x00,0x00,0x00,0x00,0x00... etc.))
This post has helped me out with similar problem. Thanks!
Bringing xBr0k3n and Howard's answers together:
#Change these three to match up to the extracted registry data and run as Admin
$YourInput = "50,33,01,00,00,00,00,00,00,00,00,00,03,00,00,00,0e,00,00,00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00"
$RegPath = 'HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\aspnet_state'
$AttrName = "FailureActions"
$hexified = $YourInput.Split(',') | % { "0x$_"}
New-ItemProperty -Path $RegPath -Name $AttrName -PropertyType Binary -Value ([byte[]]$hexified)
Resurrecting this.
Here's how you can modify registry item binary values concisely in easy-to-follow powershell. In this example DefaultConnectionSettings is the registry item with a REG_BINARY value that we're trying to modify.
$path = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"
$objName = "DefaultConnectionSettings"
$getObj = Get-ItemProperty -path $path -name $objName
$getObj.DefaultConnectionSettings[8] = 1
$objValue = $getObj.DefaultConnectionSettings
Set-ItemProperty -path $path -name $objName -Value $objValue
When you use Get-ItemProperty for a registry item with a REG_BINARY value, it gives you a number of child objects in a collection.
By referencing the name of the item (in this case we do getObj.DefaultConnectionSettings) as a child object of getObj, we get an array of values, where each binary value (i.e 50,33,01,00,00,00,00,00,04) has its own position in the array.
Because it is an array we can reference, modify, and iterate through it easily by doing $getObj.DefaultConnectionSettings[8] = 1 or whatever number you want in place of 8. The 8 refers to the position of the value in the array. In the example of 50,33,01,00,00,00,00,00,04 the 9th position is 04. Remember that, like other things, arrays start counting at 0.
Setting it = 1 will change that 04 value in the binary to 01 while leaving the rest of the values unchanged in the array.
Finally, we set the change in place with Set-ItemProperty -path $path -name $objName -Value $objValue
Hope this helps others.
FYI, you can also set binary values with the PSRemoteRegistry PowerShell module (
http://psremoteregistry.codeplex.com/), on local or remote computers.
$Key = 'SOFTWARE\MyCompany'
Set-RegBinary -Hive LocalMachine -ComputerName Server1 -Key $Key -Value RegBinary -Data #([char[]]'PowerShell')
Let's start with an integer:
$int = 0xffffffff
Get the bytes:
$bytes = [bitconverter]::GetBytes($int)
Using set-itemproperty with the little knows '-type' parameter that can be used with registry paths:
Set-ItemProperty hkcu:\key1 bin1 $bytes -type binary
Get it back:
$bytes = Get-ItemProperty hkcu:\key1 bin1 | % bin1
Turn 4 bytes into an int:
$int = [bitconverter]::toint32($bytes, 0)
'0x{0:x}' -f $int
0xffffffff
I had problems with the other solutions, here's what I found to work:
Short Answer:
New-ItemProperty -path $path -name $name -value [byte]0x00,0x01,0x02 -PropertyType Binary
Complete Example:
$path = "HKCU:\Software\Hex-Rays\"
$name = "StrWinStringTypes"
$value = [byte]0x00,0x01,0x02
#if key path found, just add/modify the value/data pair
If (Test-Path($path))
{
New-ItemProperty -path $path -name $name -value $value -PropertyType Binary -Force | Out-Null
}
#if key path not found, create it first before adding value/data
Else
{
New-Item -path $path -force
New-ItemProperty -path $path -name $name -value $value -PropertyType Binary -Force | Out-Null
}