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.
I've created a proxy function for Remove-Item, which deletes to the recycle bin instead of permanently (using the proxy so that I can seamlessly replace the rm alias, without breaking 3rd party scripts).
However, it doesn't work when a file is piped into the function. The heart of the proxy function is this:
if ($PSBoundParameters['DeletePermanently'] -or $PSBoundParameters['LiteralPath'] -or $PSBoundParameters['Filter'] -or $PSBoundParameters['Include'] -or $PSBoundParameters['Exclude'] -or $PSBoundParameters['Recurse'] -or $PSBoundParameters['Force'] -or $PSBoundParameters['Credential']) {
if ($PSBoundParameters['DeletePermanently']) { $PSBoundParameters.Remove('DeletePermanently') | Out-Null }
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters['Path'] }
}
So, my custom Recycle-Item function is only called if Path is the only parameter. So, something like Get-ChildItem .\temp\ | rm -DeletePermanently works just fine, but Get-ChildItem .\temp\ | rm has an error because the Path passed to Recycle-Item is $null.
I've tried passing $Path instead of $PSBoundParameters['Path'] and tried splatting #PSBoundParameters like the call to $wrappedCmd above, but none of it appears to do much good. I've copied the params from this function to Recycle-Item, to ensure that it is expecting input from the pipeline, but that doesn't seem to help either. Some of those changes appear to pass along the file name, but not the full path, so I don't know if there's some magic inside Remove-Item that I need to replicate to handle a file object from the pipeline.
Recycle-Item is just a basic function:
function Recycle-Item($Path) {
$item = Get-Item $Path
$directoryPath = Split-Path $item -Parent
$shell = new-object -comobject "Shell.Application"
$shellFolder = $shell.Namespace($directoryPath)
$shellItem = $shellFolder.ParseName($item.Name)
$shellItem.InvokeVerb("delete")
}
As mentioned in the comments, the provider cmdlets usually bind on LiteralPath when you pipe objects between them. This way allows Path to support wildcard globbing without the chance of passing ambiguous item paths between cmdlets.
Remove-Item has only two parameter sets, and they are named after their mandatory parameters, Path and LiteralPath
To solve your problem, simply check for all defined parameters that are not one of these two, then pass the appropriate value to Remove-Item based on the $PSCmdlet.ParameterSetName value:
if(#($PSBoundParameters.Keys |Where-Object {#('DeletePermanently','Filter','Include','Exclude','Recurse','Force','Credential') -contains $_}).Count -ge 1){
# a parameter other than the Path/LiteralPath or the common parameters was specified, default to Remove-Item
if ($PSBoundParameters['DeletePermanently']) {
$PSBoundParameters.Remove('DeletePermanently') | Out-Null
}
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
} else {
# apart from common parameters, only Path/LiteralPath was specified, go for Recycle-Item
$scriptCmd = {& Recycle-Item -Path $PSBoundParameters[$PSCmdlet.ParameterSetName] }
}
I would like to clear a PowerShell session of mostly all alias definitions, except for common aliases like cd, sort, mkdir, ...
After I finished my session, I would like to restore all previous known the aliases.
There is no need to unload modules or to unregister CmdLets. I just want to clear the alias namespace for my session.
I could specify the allowed aliases in a list like this:
$AllowedAliases = #(
"cd", "mkdir", "rm", "rmdir",
"cd", "mkdir", "rm", "rmdir",
"where", "select",
"sort"
)
How can I save the aliases and restore them?
or
How can I start a clean PoSh and load only basic aliases?
What I have tested so far:
The following lines are from my example module called poc.psm1.
$Aliases = #()
function Register-PoC
{ foreach ($a in (Get-Item Alias:))
{ $script:Aliases += $a
Write-Host "$($a.Name) => $($a.ReferencedCommand) ($($a.Visibility))"
Remove-Item "Alias:$($a.Name)" -Force
}
}
function Unregister-PoC
{ foreach ($a in $script:Aliases)
{ Write-Host "$($a.Name) <= $($a.ReferencedCommand)"
if (Test-Path "Alias:$($a.Name)")
{ Write-Host "$($a.Name) exists." }
else
{ Set-Alias -Name $a.Name -Value $a.ReferencedCommand -Scope $a.Visibility }
}
if (Test-Path Alias:quit) { Remove-Item Alias:quit }
Remove-Module PoC
}
Export-ModuleMember -Function 'Register-PoC'
Export-ModuleMember -Function 'Unregister-PoC'
Register-PoC
Set-Alias -Name quit -Value Unregister-PoC -Description "Unload this module." -Scope Global
Usage example:
Import-Module .\poc.psm1
dir Alias:
quit
dir Alias:
Unfortunately, dir Alias: is not empty after invoking my script...
Another thing is, that I should preserve some settings of these aliases, because manual test showed, that dir does not behave like dir in before:
Remove-Item dir
Set-Alias dir Get-Item
dir
Cmdlet Get-Item an der Befehlspipelineposition 1
Geben Sie Werte für die folgenden Parameter an:
Path[0]:
So dir seams to append a default path to Get-Item if non is set to the alias.
Aliases are scoped. When you remove all aliases within a function, the aliases in the global scope aren't affected. Here is code that worked for me (simplifies your code a bit, although I didn't touch unregister-PoC, which could also be simplified I think):
function Register-PoC {
$script:aliases = get-item alias:
remove-item alias:* -force
}
function Unregister-PoC
{ foreach ($a in $script:Aliases)
{ Write-Host "$($a.Name) <= $($a.ReferencedCommand)"
if (Test-Path "Alias:$($a.Name)")
{ Write-Host "$($a.Name) exists." }
else
{ Set-Alias -Name $a.Name -Value $a.ReferencedCommand -Scope $a.Visibility }
}
if (Test-Path Alias:quit) { Remove-Item Alias:quit }
Remove-Module PoC
}
. Register-PoC
Set-Alias -Name quit -Value Unregister-PoC -Description "Unload this module." -Scope Global
Note the dot operator on Register-PoC. You will need to dot source quit to restore the aliases to global scope.
BTW, rather than the foreach loop in Unregister-PoC you could use copy-item.
For your situation, I would recommend using PowerShell profiles. These can be defined per user, per machine, and other situations. You can automatically run functions stored in the profile just by calling the function after it's defined in the profile.
For just your current user on the current machine, see Example 3 here.
New-Item -Path $PROFILE -ItemType File -Force
For other profile options, check out Understanding the Six PowerShell Profiles.
To ignore a profile, you can do that by directly running powershell.exe -NoProfile -NoExit but beware of nested sessions when doing that in another PowerShell session.
For a way to wipe all aliases except your desired list, you can export the aliases and re-import them after wiping all aliases. This can be added to the profile, if desired. Otherwise assign it to a function in the profile and call as needed. Change path if not using the profile folder(or wherever you like to keep things):
$allowedaliases = "cd","dir","rm","rmdir","where","select","sort"
Export-Alias -Path "$((Get-ChildItem $PROFILE).DirectoryName)\aliases.csv" -Name $allowedaliases
Remove-Item Alias:* -Force
Import-Alias -Path "$((Get-ChildItem $PROFILE).DirectoryName)\aliases.csv" -Force
Note: One of the original listed aliases(mkdir) is a function, not an alias, at least in Powershell v5.0. Also added dir into the list since that was mentioned by the OP.
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
}