Using two arrays to create registry keys/values - arrays

Trying to automate our font installation process for new PCs.
To install fonts, Windows adds the .ttf, .otf, etc. file to C:\Windows\Fonts and then creates a corresponding registry key in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts. A typical registry key would look like this:
Arial (TrueType) | Arial.ttf
To automate this, I've made two arrays using Get-ChildItem:
$names = Get-ChildItem -Path "C:\corp\install\fonts" | Select-Object name | Out-String | ForEach-Object {$_ -Replace "----","" ` -Replace "Name","" ` -Replace ".otf","" ` -Replace ".ttf","" } | ForEach-Object { $_.Trim() }
$files = Get-ChildItem -Path "C:\corp\install\fonts" | Select-Object name | Out-String | ForEach-Object {$_ -Replace "----","" ` -Replace "Name","" } | ForEach-Object { $_.Trim() }
Each $name in $names will be the name of the registry key, and each $file in $files will be the data for that registry key.
How would I go about doing this? I've attempted to use hash tables, PSObjects, nested ForEach loops, all to no avail. I have had difficulty finding anything on here and elsewhere that matches this situation exactly.
Error checking is not really necessary since there will always be a corresponding value.
REVISED FINAL SOLUTION:
Write-Host "Installing corporate fonts..."
Copy-Item -Path "C:\corp\install\fonts\*" -Destination "C:\Windows\Fonts" -Force -Recurse
$fontList = #()
$fonts = Get-ChildItem "C:\corp\install\fonts" | Select-Object -ExpandProperty Name
ForEach ( $font in $fonts ) {
$fontList += [PSCustomObject] #{
Name = $font -Replace ".otf","" ` -Replace ".ttf",""
File = $font
} |
ForEach-Object {
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" -Name $_.Name -Value $_.File
}
}

I must admit I don't fully understand your question so forgive me if this response is way off base, but it this what you're looking for? A table with both pieces of data in one?
Function CreateVariables {
$namevariables = #()
$filenames = ( Get-ChildItem "C:\corp\install\fonts" ).name
Foreach ( $name in $filenames ){
$namevariables += [PSCustomObject] #{
Name = $name -Replace "----","" ` -Replace "Name","" ` -Replace ".otf","" ` -Replace ".ttf",""
File = $name -Replace "----","" ` -Replace "Name",""
}
}
Return $namevariables
}
CreateVariables

Piping both name and value to set-itemproperty seem impossible. Foreach-object seems the way to go.
$path = 'hklm:\software\microsoft\windows nt\currentversion\fonts'
[pscustomobject]#{name='a name';value='a value'} |
foreach { set-itemproperty $path $_.name $_.value -whatif }
What if: Performing the operation "Set Property" on target "Item: HKEY_LOCAL_MACHINE\software\microsoft\windows nt\currentversion\fonts Property: a name".
You may prefer using this vbscript-like method to install fonts:
https://www.mondaiji.com/blog/other/it/10247-windows-install-fonts-via-command-line

Related

PowerShell searching matches over 3 CSVs ... slow execution

I have to find matches over 3 CSVs. It is to find out whether users have AccessRights on PublicFolders in Exchange 2016. For ease of use I have already searched and stored all the needed values in 3 CSVs
"PF-Folder_Full.csv": a list of all the Publich Folders (more than 5000)
"PF-Mailboxes.csv": a list of all the users (around 50)
"PF-Permissions.csv": the result of
Get-PublicFolderClientPermission -Identity $Folder.Identity
looped through all the Public Folders (that takes ages)
I have written a script that does the job but even on a fast computer it is extremely slow because it has too loop through all the Public Folders and through all the users and then find a match for both values in the permissions
$Folders = Import-Csv -Path ".\PF-Folder_Full.csv" -Encoding Unicode
$Mailboxes = Import-Csv -Path ".\PF-Mailboxes.csv" -Encoding Unicode
$Permissions = Import-Csv -Path ".\PF-Permissions.csv" -Encoding Unicode
foreach ($Folder in $Folders) {
foreach ($Mailbox in $Mailboxes) {
$Permission = $Permissions | where {
($_.Identity -eq $Folder.Identity) -and
($_.User -eq $Mailbox.DisplayName)
}
if ($Permission) {
# some code
} else {
# some other code
}
Remove-Variable Permission
}
}
Is there a way to speed-up things? Possibly through the use of regular expressions.
I couldn't find any example that allows for extended matches between multiple arrays.
As Kory Gill mentioned in the comments: build two hashtables from the Identity and DisplayName properties from the first two CSVs:
$Folders = #{}
Import-Csv -Path ".\PF-Folder_Full.csv" -Encoding Unicode | ForEach-Object {
$Folders[$_.Identity] = $_
}
$Mailboxes = #{}
Import-Csv -Path ".\PF-Mailboxes.csv" -Encoding Unicode | ForEach-Object {
$Mailboxes[$_.DisplayName] = $_
}
Then process the third CSV using these hashtables for lookups:
$Permissions = Import-Csv -Path ".\PF-Permissions.csv" -Encoding Unicode
foreach ($p in $Permissions) {
if ($Folders.Contains($p.Identity) -and $Mailboxes.Contains($p.User)) {
# some code
} else {
# some other code
}
}
If you want code run just once per unique identity/user combination you could build a hashtable with the combined identity and mailbox name for filtering:
$Folders = Import-Csv -Path ".\PF-Folder_Full.csv" -Encoding Unicode |
Select-Object -Expand Identity
$Mailboxes = Import-Csv -Path ".\PF-Mailboxes.csv" -Encoding Unicode |
Select-Object -Expand DisplayName
$fltr = #{}
foreach ($f in $Folders) {
foreach ($m in $Mailboxes) {
$fltr["$f $m"] = $true
}
}
and then group the records from the third CSV:
Import-Csv -Path ".\PF-Permissions.csv" -Encoding Unicode |
Group-Object Identity, User |
ForEach-Object {
if ($fltr.Contains($_.Name)) {
# some code
} else {
# some other code
}
}

wildcard in array

How can I use the 1709 as a wildcard? The value 1709 is stored in an array as $MoveItem.Version, but I can't figure out how do a -like, when the value comes from an array, as I can't put in a wildcard *. I also tried to do a match.
The file name looks like this: REFW10-X86-1709_01-12-2017.wim.
The below code works fine, but I would like to automate it, so everything comes from the array. Is that possible?
Get-ChildItem -Path $OSPathTemp -Recurse | Where {
($_.Name -eq $MoveItem.File) -and
($_.Name -like "*1709*")
} | Select-Object -ExpandProperty FullName
$MoveItem.Version contains 1607,1706,1709. I would like to choose only the one with 1709. The final output should look like this:
foreach ($MoveItem in $MoveItems) {
Get-ChildItem -Path $OSPathTemp -Recurse | Where {
($_.Name -eq $MoveItem.File) -and
($_.Name -like $MoveItem.Version)
} | Select-Object -ExpandProperty FullName
}
The Array looks like this:
$MoveItem = #(
[pscustomobject]#{File="REFW10-X86-1709_01-12-2017.wim";Version=1709}
[pscustomobject]#{File="REFW10-X86-1706_01-12-2017.wim";Version=1706}
)
So you have a hash table (or similar) named $MoveItem that has a .File property that is a filename, and you have a .Versions property that's a string array?
Test name: REFW10-X86-1709_01-12-2017.wim
Get-ChildItem -Path $OSPathTemp -Recurse |
ForEach-Object {
If ($_.Name -match '-\d{4}_') { $Version = $Matches[0] }
If ($Version -in $MoveItem.Versions -and
$_.Name -eq $MoveItem.File) { $_.FullName }
}

Function return array in PowerShell

I have a problem when I'm trying to return an array from a PowerShell function.
My function:
function filter-SWCluster {
param($Path, $FolderList)
$OldSWCluster = New-Object System.Collections.ArrayList
ForEach ($y in $FolderList) {
Get-ChildItem -Path $Path -Filter $y* | sort { [version]($_.Name -replace '^.*_(\d+(\.\d+){1,3})$', '$1') } -Descending | Select-Object -skip 3 | ForEach-Object { $OldSWCluster.Add($Path + "\" + $_) }
}
Write-Output $OldSWCluster
}
The function call:
$FilerSWCluster = filter-SWCluster $NetworkPath $SWCluster
The output I get:
0 1 \\server.domain\C$\TEMP\Folders\Testcluster_1.0.2 \\server.domain\C$\TEMP\Folders\Testcluster_1.0.1
The output I want and need would be:
\\server.domain\C$\TEMP\Folders\Testcluster_1.0.2 \\server.domain\C$\TEMP\Folders\Testcluster_1.0.1
I need just the paths. I know there will already be some solutions, but please excuse me. I didn't find one which was working for me or I were to dumb to use it right (could be).
You could also wrap the Add method with the [void] class accelerator. It will hide any output, like this:
function filter-SWCluster {
param($Path, $FolderList)
$OldSWCluster = New-Object System.Collections.ArrayList
ForEach ($y in $FolderList) {
Get-ChildItem -Path $Path -Filter $y* | sort { [version]($_.Name -replace '^.*_(\d+(\.\d+){1,3})$', '$1') } -Descending | Select-Object -skip 3 | ForEach-Object { [void]$OldSWCluster.Add($Path + "\" + $_) }
}
Write-Output $OldSWCluster
}
wOxxOm already provided you a solution to your problem. However, consider to just return the path using a simple select. You also don't have to join the path since the FullName property already contains the full path:
function filter-SWCluster {
param($Path, $FolderList)
ForEach ($y in $FolderList) {
Get-ChildItem -Path $Path -Filter $y* |
sort { [version]($_.Name -replace '^.*_(\d+(\.\d+){1,3})$', '$1') } -Descending |
Select-Object -skip 3 |
select -ExpandProperty FullName
}
}

Iterate through Rows in SQL to Output to Text File

I have a SQL table that contains several hundred rows of data. One of the columns in this table contains text reports that were stored as plain text within the column.
Essentially, I need to iterate through each row of data in SQL and output the contents of each row's report column to its own individual text file with a unique name pulled from another column.
I am trying to accomplish this via PowerShell and I seem to be hung up. Below is what I have thus far.
foreach ($i=0; $i -le $Reports.Count; $i++)
{
$SDIR = "C:\harassmentreports"
$FILENAME = $Reports | Select-Object FILENAME
$FILETEXT = $Reports | Select-Object TEXT
$NAME = "$SDIR\$FILENAME.txt"
if (!([System.IO.File]::Exists($NAME))) {
Out-File $NAME | Set-Content -Path $FULLFILE -Value $FILETEXT
}
}
Assuming that $Reports is a list of the records from your SQL query, you'll want to fix the following issues:
In an indexed loop use indexed access to the elements of your array:
$FILENAME = $Reports[$i] | Select-Object FILENAME
$FILETEXT = $Reports[$i] | Select-Object TEXT
Define variables outside the loop if their value doesn't change inside the loop:
$SDIR = "C:\harassmentreports"
foreach ($i=0; $i -le $Reports.Count; $i++) {
...
}
Expand properties if you want to use their value:
$FILENAME = $Reports[$i] | Select-Object -Expand FILENAME
$FILETEXT = $Reports[$i] | Select-Object -Expand TEXT
Use Join-Path for constructing paths:
$NAME = Join-Path $SDIR "$FILENAME.txt"
Use Test-Path for checking the existence of a file or folder:
if (-not (Test-Path -LiteralPath $NAME)) {
...
}
Use either Out-File
Out-File -FilePath $NAME -InputObject $TEXT
or Set-Content
Out-File -Path $NAME -Value $TEXT
not both of them. The basic difference between the two cmdlets is their default encoding. The former uses Unicode, the latter ASCII encoding. Both allow you to change the encoding via the parameter -Encoding.
You may also want to reconsider using a for loop in the first place. A pipeline with a ForEach-Object loop might be a better approach:
$SDIR = "C:\harassmentreports"
$Reports | ForEach-Object {
$file = Join-Path $SDIR ($_.FILENAME + '.txt')
if (-not (Test-Path $file)) { Set-Content -Path $file -Value $_.TEXT }
}

Powershell to replace text in multiple files stored in many folders

I want to replace a text in multiple files and folders. The folder name changes, but the filename is always config.xml.
$fileName = Get-ChildItem "C:\config\app*\config.xml" -Recurse
(Get-Content $fileName) -replace 'this', 'that' | Set-Content $fileName
When I run the above script it works, but it writes the whole text in config.xml about 20 times. What's wrong?
$filename is a collection of System.IO.FileInfo objects.
You have to loop to get the content for each file :
this should do what you want :
$filename | %{
(gc $_) -replace "THIS","THAT" |Set-Content $_.fullname
}
In general, you should use the pipeline and combine the ForEach-Object and/or Where-Object CmdLets.
In your case, this would like like something more akin to:
Get-ChildItem "C:\config\app*\config.xml" -Recurse | ForEach-Object -Process {
(Get-Content $_) -Replace 'this', 'that' | Set-Content $_
}
Which can be shortened somewhat to:
dir "C:\config\app*\config.xml" -recurse |% { (gc $_) -replace 'this', 'that' | (sc $_) }
$filename is an array of filenames, and it's trying to do them all at once. Try doing them one at a time:
$fileNames = Get-ChildItem "C:\config\app*\config.xml" -Recurse |
select -expand fullname
foreach ($filename in $filenames)
{
( Get-Content $fileName) -replace 'this', 'that' | Set-Content $fileName
}
I got list of files to replace text this way.
$filenames = Get-ChildItem|Select-String -Pattern
""|select Filename
This gets 12 files.
To replace this text in all files
foreach ($filename in $filesnames){ (Get-Content $filename.Filename) -replace "", ""|Set-Content $filename.Filename }
Don't forget last part for Filename. $filename.Filename

Resources