Append an array using powershell - powershell

I have an array with 3 elements(feature) in my code. Currently i have declared them as $feature = "System","Battery","Signal","Current";
But in future there can be more features. So I thought of giving an option in my code to add new feature(implemented as GUI) and using $feature.Add("$new_feature") command.
This works perfectly for that particular run of the script. But when i run the script again, this new added feature is not appearing. How can i solve this issue, so that when ever new feature is added, then it will remain in the script for ever?
Is this possible?

The simplest approach would be to store the array data in a file:
# read array from file
$feature = #(Get-Content 'features.txt')
# write array back to file
$feature | Set-Content 'features.txt'
You can use $PSScriptRoot to get the location of the script file (so you can store the data file in the same folder). Prior to PowerShell v3 use the following command to determine the folder containing the script:
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
Another option is to store the data in the registry (easier to locate the data, but a little more complex to handle):
$key = 'HKCU:\some\key'
$name = 'features'
# read array from registry
$feature = #(Get-ItemProperty -Path $key -Name $name -EA SilentlyContinue | Select-Object -Expand $name)
# create registry value if it didn't exist before
if (-not $?) {
New-ItemProperty -Path $key -Name $name -Type MultiString -Value #()
}
# write array back to registry
Set-ItemProperty -Path $key -Name $name -Value $feature

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.

Compare txt data with variable - powershell

This is the script what I want to use:
$path = split-path $MyInvocation.MyCommand.path
$vcenter = Read-Host "Please enter the vCenter name where You want to connect"
Import-Module -Name VMware.VimAutomation.Core
Connect-VIserver $vcenter
$folderName = 'Datacenters'
$folder = Get-Folder -Name $folderName
$patches = Get-Content $path\patches.txt -Raw
$baseline = New-PatchBaseline -Name "Baseline$(Get-Random)" -Static -IncludePatch $patches
Attach-Baseline -Entity $folder -Baseline $baseline -Confirm:$false
Scan-Inventory -Entity $folder
Get-Compliance -baseline $baseline -entity $folder | select Entity, Status
Detach-Baseline -Entity $folder -Baseline $baseline -Confirm:$false
Remove-Baseline -Baseline $baseline -Confirm:$false
If I write multiple patch numbers into the txt - I tried the following methods - :
ESXi670-201912001,ESXi670-201905001
ESXi670-201905001ESXi670-201912001
Also I tried to separate the lines with enter, without comma, the script not able to compare with the $baseline variable.
The desired result would be: write the patch numbers into the text file, attach a new baseline to the vmware environment, and compare the installed patches on the hosts, with the patches what I wrote into the text.
Many thanks for the help!
The -IncludePatch parameter expects an array of items. First, I recommend removing the -Raw switch from Get-Content because that will read in the file contents as one, long string. Second, I recommend just listing the patches one line at a time. That combination will cause the file to be read as an array of strings with each string being a patch name.
# patches.txt Contents
ESXi670-201912001
ESXi670-201905001
# Update This Line
$patches = Get-Content $path\patches.txt

Referencing targeted object in ForEach-Object in Powershell

I am fairly new to Powershell. I am attempting to re-write the names of GPO backup folders to use their friendly name rather than their GUID by referencing the name in each GPO backup's 'gpresult.xml' file that is created as part of the backup. However, I do not understand how I can reference the specific object (in this case, the folder name) that is being read into the ForEach-Object loop in order to read into the file beneath this folder.
function Backup_GPO {
$stamp = Get-Date -UFormat "%m%d"
New-Item -ItemType "directory" -Name $stamp -Path \\dc-16\share\GPO_Backups -Force | out-null # create new folder to specify day backup is made
Backup-GPO -All -Path ("\\dc-16\share\GPO_Backups\" + "$stamp\" )
Get-ChildItem -Path \\dc-16\share\GPO_Backups\$stamp | ForEach-Object {
# I want to reference the current folder here
[xml]$file = Get-Content -Path (folder that is being referenced in for loop)\gpresult.xml
$name = $file.GPO.Name
}
I'm coming from Python, where if I want to reference the object I'm currently iterating on, I can do so very simply -
for object in list:
print(object)
How do you reference the currently in-use object in Powershell's ForEach-Object command?
I'm coming from Python, where if I want to reference the object I'm currently
iterating on, I can do so very simply -
for object in list:
print(object)
The direct equivalent of that in PowerShell is the foreach statement (loop):
$list = 1..3 # create a 3-element array with elements 1, 2, 3
foreach ($object in $list) {
$object # expression output is *implicitly* output
}
Note that you cannot directly use a foreach statement in a PowerShell pipeline.
In a pipeline, you must use the ForEach-Object cmdlet instead, which - somewhat confusingly - can also be referred to as foreach, via an alias - it it is only the parsing mode that distinguishes between the statement and the cmdlet's alias.
You're using the ForEach-Object cmdlet in the pipeline, where different rules apply.
Script blocks ({ ... }) passed to pipeline-processing cmdlets such as ForEach-Object and Where-Object do not have an explicit iteration variable the way that the foreach statement provides.
Instead, by convention, such script blocks see the current pipeline input object as automatic variable $_ - or, more verbosely, as $PSItem.
While the foreach statement and the ForEach-Object cmdlet operate the same on a certain level of abstraction, there's a fundamental difference:
The foreach statement operates on collections collected up front in memory, in full.
The ForEach-Object cmdlet operates on streaming input, object by object, as each object is being received via the pipeline.
This difference amounts to the following trade-off:
Use the foreach statement for better performance, at the expense of memory usage.
Use the ForEach-Object cmdlet for constant memory use and possibly also for the syntactic elegance of a single pipeline, at the expense of performance - however, for very large input sets, this may be the only option (assuming you don't also collect a very large dataset in memory on output).
Inside the ForEach-Object scriptblock, the current item being iterated over is copied to $_:
Get-ChildItem -Filter gpresult.xml |ForEach-Object {
# `$_` is a FileInfo object, `$_.FullName` holds the absolute file system path
[xml]$file = Get-Content -LiteralPath $_.FullName
}
If you want to specify a custom name, you can either specify a -PipelineVariable name:
Get-ChildItem -Filter gpresult.xml -PipelineVariable fileinfo |ForEach-Object {
# `$fileinfo` is now a FileInfo object, `$fileinfo.FullName` holds the absolute file system path
[xml]$file = Get-Content -LiteralPath $fileinfo.FullName
}
or use a foreach loop statement, much like for object in list in python:
foreach($object in Get-ChildItem -Filter gpresult.xml)
{
[xml]$file = Get-Content -LiteralPath $object.FullName
}
Another way...
$dlist = Get-ChildItem -Path "\\dc-16\share\GPO_Backups\$stamp"
foreach ($dir in $dlist) {
# I want to reference the current folder here
[xml]$file = Get-Content -Path (Join-Path -Path $_.FullName -ChildPath 'gpresult.xml')
$name = $file.GPO.Name
}
Here's my solution. It's annoying that $_ doesn't have the full path. $gpath is easier to work with than $_.fullname for joining the two strings together on the next line with get-content. I get a gpreport.xml file when I try backup-gpo. Apparently you can't use relative paths like .\gpo_backups\ with backup-gpo.
mkdir c:\users\js\gpo_backups\
get-gpo -all | where displayname -like '*mygpo*' |
backup-gpo -path c:\users\js\gpo_backups\
Get-ChildItem -Path .\GPO_Backups\ | ForEach-Object {
$gpath = $_.fullname
[xml]$file = Get-Content -Path "$gpath\gpreport.xml"
$file.GPO.Name
}

How do I make powershell script transverse zip files and report based off select-string -pattern

I have the following that is working but I need to also have the ability to read the contents of compressed file (zip)
function Search-Files {
param ([string[]]$Servers, [string]$SearchPath, [string]$SearchItem, [string[]]$LogName)
ForEach ($Server in $Servers) {
if ($LogName -eq $null) {
dir -Path \\$server\$SearchPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-String -pattern $SearchItem -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-Object Filename, Path, Matches, LineNumber
}
Else {
dir -Path \\$server\$SearchPath -Recurse -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | ? {$_.Name -match $LogName} | Select-String -pattern $SearchItem -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Select-Object Filename, Path, Matches, LineNumber
}
}
}
Currently I am getting the following out put displayed which is what I would like to do for zip files as well
ip.ininlog \CO200197L\C$\Temp\Test\Test\ip\ip.ininlog {3030872954} 136594
I have found the following just not sure how to proceed to get them implemented
Grep File in Zip
List File in Zip
I need the ability to transverse all zip files that are store in a directory
Sample of Directory Structure
2014-07-01 - root
zip.zip
zip_1.zip
zip_2.zip
etc
In case you have NET 4.5 framework installed, you can use 4.5's built-in ZIP support to extract files to a temporary path and run the selection on the temporary file. If no 4.5 is available, I recommend using SharpCompress (https://sharpcompress.codeplex.com/) which works in a similar way.
The following code snippet demonstrates extracting a ZIP archive into a temporary file, running the selection process from your script and the cleanup after the extraction. You can significantly simplify the code by extracting the entire ZIP file at once (just use ExtractToDirectory() on the archive) if it contains only the files you are seeking.
# import .NET 4.5 compression utilities
Add-Type -As System.IO.Compression.FileSystem;
# the input archive
$archivePath = "C:\sample.zip";
# open archive for reading
$archive = [System.IO.Compression.ZipFile]::OpenRead($archivePath);
try
{
# enumerate all entries in the archive, which includes both files and directories
foreach($archiveEntry in $archive.Entries)
{
# if the entry is not a directory (which ends with /)
if($archiveEntry.FullName -notmatch '/$')
{
# get temporary file -- note that this will also create the file
$tempFile = [System.IO.Path]::GetTempFileName();
try
{
# extract to file system
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($archiveEntry, $tempFile, $true);
# create PowerShell backslash-friendly path from ZIP path with forward slashes
$windowsStyleArchiveEntryName = $archiveEntry.FullName.Replace('/', '\');
# run selection
Get-ChildItem $tempFile | Select-String -pattern "yourpattern" | Select-Object #{Name="Filename";Expression={$windowsStyleArchiveEntryName}}, #{Name="Path";Expression={Join-Path $archivePath (Split-Path $windowsStyleArchiveEntryName -Parent)}}, Matches, LineNumber
}
finally
{
Remove-Item $tempFile;
}
}
}
}
finally
{
# release archive object to prevent leaking resources
$archive.Dispose();
}
If you have multiple ZIP files in the directory, you can enumerate them as follows (using your example script):
$zipArchives = Get-ChildItem -Path \\$server\$SearchPath -Recurse "*.zip";
foreach($zipArchive in $zipArchives)
{
$archivePath = $zipArchive.FullName;
...
}
You can place the demo code in ... or move it to a PowerShell function.
Sometimes is not desirable to extract a zip entry as a file. Instead it may be preferable to work with the file in memory. Extracting a Zip entry containing XML or JSON text so it can be parsed in memory is an example.
Here is a technique that will allow you to do this. This example assumes there is a Zip entry with a name ending in .json and it is this file which is to be retrieved. Clearly the idea can be modified to handle different cases.
This code should work with version of the .NET Framework that includes the System.IO.Compression namespace.
try
{
# import .NET 4.5 compression utilities
Add-Type -As System.IO.Compression.FileSystem;
# A variable to hold the recovered JSON content
$json = $null
$zip = [IO.Compression.ZipFile]::OpenRead($zipFileName)
$zip.Entries |
Where-Object { $_.Name.EndsWith(".json") } |
ForEach-Object {
# Use a MemoryStream to hold the inflated file content
$memoryStream = New-Object System.IO.MemoryStream
# Read the entry
$file = $_.Open()
# Copying inflates the entry content
$file.CopyTo($memoryStream)
# Make sure the entry is closed
$file.Dispose()
# After copying, the cursor will be at the end of the stream
# so set the position to the beginning or there will be no output
$memoryStream.Position = 0
# Use a StreamReader because it allows the content to be
# read as a string in one go
$reader = New-Object System.IO.StreamReader($memoryStream)
# Read the content as a string
$json = $reader.ReadToEnd()
# Close the reader and memory stream
$reader.Dispose()
$memoryStream.Dispose()
}
# Finally close the zip file. This is necessary
# because the zip file does get closed automatically
$zip.Dispose()
# Do something with the JSON in memory
if ( $json -ne $null )
{
$objects = $json | ConvertFrom-Json
}
}
catch
{
# Report errors
}

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
}