PowerShell for-each cleanup - powershell

I know PowerShell is up to v5, but as I am new to PowerShell, I've been looking through Stack Overflow to generate the script I have. I've found that I need a generic non-version specific way of accomplishing this process...
Here is the issue - Step 1 - I'm pulling application installation locations information from the registry and am using a temporary file to house the results.
dir "HKLM:\SOFTWARE\Wow6432Node\companyname" | Get-ItemProperty | Select installdir | Out-File "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-Installs.txt"
This provides me a list of installation directories for the company's software that is installed on a particular machine. I then want to take these results, append *.config to each line, as well as taking these results and appending *.xml to each line, and output to a new text file.
The input for the process would be the contents of the initial results file, and the output file should have each line listed in the first results, added to the final results file, once appended with *.xml, and once appended with *.config.
The net effect I am looking for is the creation of a #file for a 7z command. I am attempting this by using the following -
(Get-Content "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-Installs.txt") -replace '\S+$','$&*.config' | Out-File "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-config.txt" -Encoding utf8
(Get-Content "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-Installs.txt") -replace '\S+$','$&*.xml' | Out-File "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-config.txt" -Append -Encoding utf8
However, I'm only getting one line that has *.xml and one line that has *.config appended -
After getting this far, I'm thinking that some for-each loop is needed, but I'm not getting anywhere with what I have tried adapting from here. I'm looking now for some way to combine the three lines into one function, if that is possible, and eliminate the temporary file step in the first command, by reading and outputting in the same step. This would also need to remove the "installdir" and "----------" lines from the output. Anyone have some ideas and maybe examples?

Taken your above command dir "HKLM:\SOFTWARE\Wow6432Node\companyname" | Get-ItemProperty | Select installdir | Out-File "$env:USERPROFILE\Desktop\KDI-Admin\Export\$env:COMPUTERNAME-SC-Installs.txt" you could put the result of your query into a variable $result:
$result = dir "HKLM:\SOFTWARE\Wow6432Node\microsoft" | Get-ItemProperty | Select installdir;
From there you can easily loop through the array, skipping empty ones and process the rest of it:
foreach($path in $result.installdir)
{
# skip empty paths
if([string]::IsNullOrWhiteSpace($path)) { continue; }
# now do your processing ...
$path;
}
Is this what you were asking for?

Related

Why does my powershell script loop not work when run, but when stepped through in debugger it works fine?

So I have the below script for a project at work:
# This script will look for a CSV in the script folder
# If found it will split the CSV based on a change in a column header
# It will then create seperate CSV files based on the column data
# get script directory and add it to a variable
Set-Location $PSScriptRoot
$baseDir = $PSScriptRoot
# get a list of csv file names
$csvfile = Get-ChildItem -Path $baseDir -Filter *.csv
# If multiple CSV files loop through all of them
foreach ($i in $csvfile) {
# Import and split the original csv
# Change the value after -Property to match the column header name of the column used to split on value change -
# Header names with spaces require surrounding quotes -
# This value will also be used to name the resulting CSV file
Import-Csv $i | Group-Object -Property "Submission ID" |
Foreach-Object {$path="Output\"+$_.name+".csv" ; $_.group |
Export-Csv -Path $path -NoTypeInformation}
# get the current time and date
$procdte = Get-Date -uformat "%m-%d-%Y %H %M %S"
# rename the original file so that it's not processed again
Rename-Item $i -NewName "Processed $procdte.txt"
}
# End processing loop
Important: some parts of this script are commented out - like the Rename-Item line is half-commented. Dunno why, think it's a stackoverflow markdown issue. It isn't like that in ISE
I tested it out with two csv files in the current directory. It's supposed to rename both files at the end to a .txt so they don't get processed again. When I just run the script, it misses the 2nd csv file. But when I step through it with debugger in ISE, it renames both files fine. What gives?
Ran powershell script, expecting both CSV files to be renamed, however it only renamed one of them. Thought there was an issue with the script, but when I run it in debug, it renames both files fine. Have managed to replicate multiple times.
Edit: to specify - this is not designed to work with two csv files specifically. It's supposed to work with one or more. I was just using two to test.

Add eighth and ninth lines to all *.txt files

i have more than 100 txt files in C:\myfolder*.txt
when i run this script from "C:\myfolder" i can add eighth and ninth lines to somename.txt
#echo off
powershell "$f=(Get-Content somename.txt);$f[8]='heretext1';$f | set-content somename.txt"
powershell "$f=(Get-Content somename.txt);$f[9]='heretext2';$f | set-content somename.txt"
but how can i add eighth and ninth lines to all *.txt files located in path C:\myfolder*.txt
Can someone explain me how to do it please...
Sorry for my English and Sorry if i didn't explaned my problem. i will try now:
I uses "*.uci" files, instead of *.txt files. i wrote txt because uci extensions are unknown for most of the people. These *.uci files are settings for chess engines with uci protocol.
So when you use chessbase program you have a lot of chess engines and each engine creates their "enginename.uci" file.
If you want to change the numbers of core used on your PC from 1 to 16 you need to do it manually by adding following information in *.uci file like this:
[OPTIONS]
Threads=1
That's why is better to make small batch or ps1 to change settings to all engines by adding these two lines with one click
Perhaps something like this PowerShell script would suit your task:
Get-ChildItem -Path 'C:\myfolder' -Filter '*.txt' | ForEach-Object {
$LineIndex = 0
$FileContent = Switch -File $_.FullName {Default {
$LineIndex++
If ($LineIndex -Eq 8) {#'
heretext1
heretext2
'#}
$_}}
Set-Content -Path $_.FullName -Value $FileContent}
Note:
Your code isn't adding lines, it is modifying existing lines. The solution below does the same.
Indices [8] and [9] access the 9th and 10th lines, not the 8th and 9th, given that array indexing is 0-based.
You need to call Get-ChildItem with your file-name pattern, C:\myfolder\*.txt, and process each matching file via ForEach-Object:
#echo off
powershell "Get-ChildItem C:\myfolder\*.txt | ForEach-Object { $f=$_ | Get-Content -ReadCount 0; $f[8]='heretext1'; $f[9]='heretext2'; Set-Content $_.FullName $f }"
Due to calling from a batch file (cmd.exe), the PowerShell command is specified on a single line; here's the readable version:
Get-ChildItem C:\myfolder\*.txt | # get all matching files
ForEach-Object { # process each
$f = $_ | Get-Content -ReadCount 0 # read all lines
$f[8] = 'heretext1'; $f[9] = 'heretext2' # update the 9th and 10th line
Set-Content $_.FullName $f # save result back to input file
}
Note:
Consider adding -noprofile after powershell, so as to suppress potentially unnecessary loading of profile files - see the documentation of the Windows PowerShell CLI, powershell.exe.
Using -ReadCount 0 with Get-Content greatly speeds up processing, because all lines are then read into a single array, instead of streaming the lines one by one, which requires collecting them in an array, which is much slower.
Note: If a given file has fewer than 10 lines, the above solution won't work, because you can only assign to existing elements of an array (an array is a fixed-size data structure). If you need to deal wit this case, insert the following after the $f = $_ | Get-Content -ReadCount 0 line, which inserts empty lines as needed to ensure that at least 10 lines are present:
if ($f.Count -lt 10) { $f += #('') * (10 - $f.Count) }
Easiest solution I can think of is using the -Index parameter provided in Select-Object for that.
Get-ChildItem -Path .\Desktop\*.txt | % { Get-Content $_.FullName | Select-Object -Index 7,8 } |
Out-File -FilePath .\Desktop\index.txt
Edit: based on your post.

powershell - read all .sql files in a folder and save them all into a single .sql file without changing line ends or line feeds

I manage database servers and often I have to apply scripts into different servers or databases.
Sometimes these scripts are all saved in a directory and need to be open and run in the target server\database.
As I have been looking at automating this task I came across how Run All PowerShell Scripts In A Directory and also How can I execute a set of .SQL files from within SSMS? and that is exactly what I needed, however I stumbled over a few issues:
I don't know the file names
:setvar path "c:\Path_to_scripts\"
:r $(path)\file1.sql
:r $(path)\file2.sql
I tried to add all .sql files into one big thing, but when I copied from powershell into sql, in many of the procedures that had long lines, the lines got messed up
cls
$Radhe = Get-Content 'D:\apply all scripts to SQLPRODUCTION\*.sql' -Raw
$Radhe.Count
$Radhe.LongLength
$Radhe
If I could read all the files in that specific folder and save them all into a single the_scripts_to_run.sql file, without changing the line endings, that would be perfect.
I don't need to use get-content or any command in particular, I just would like to get all my scripts into a big single script with everything in it, without changes.
How can I achieve that?
I even found Merge multiple SQL files into a single SQL file but I want to get it done via powershell.
This should work fine, I'm not sure what you mean by not needing to use Get-Content you could use [System.IO.File]::ReadAllLines( ) or [System.IO.File]::ReadAllText( ) but this should work fine too. Try it and let me know if it works.
$path = "c:\Path_to_scripts"
$scripts = (Get-ChildItem "$path\*.sql" -Recurse -File).FullName
$merged = [system.collections.generic.list[string[]]]::new()
foreach($script in $scripts)
{
$merged.Add((Get-Content $script))
}
$merged | Out-File "$path\mergedscripts.sql"
This is actually much simpler than the proposed solutions. Get-Content takes a list of paths and supports wildcards, so no loop is required.
$path = 'c:\temp\sql'
Set-Content -Path "$path\the_scripts_to_run.sql" -Value (Get-Content -Path "$path\*.sql" -Raw)
Looks like me and #Santiago had the same idea:
Get-ChildItem -Path "$path" -Filter "*.sql" | ForEach-Object -Process {
Get-Content $_.FullName | Out-File $Path\stuff.txt -Append utf8
}

Trouble editing text files with powershell Get-Content and Set-Content

Goal: Update text entry on one line within many files distributed on a server
Summary: As part of an application migration between datacenters the .rdp files on end-user desktops need to be updated to point to the new IP address of their Remote Desktop Server. All the .rdp files reside on Windows servers in a redirected folders SMB share where I have Administrative access.
Powershell experience: minimal. Still trying to wrap my head around the way variables, output and piping work.
Was originally trying to make a single line of powershell code to complete this task but got stuck and had to make script file with the two lines of code below.
-Line 1: Search for all .rdp files in the folder structure and store the full path with file name in a variable. Every file will be checked since the users tend to accidentally change file names, eliminating absolute predictability.
-Line 2: I want to make one pass through all the files to replace only instances of two particular IP addresses with the new address. Then write the changes into the original file.
$Path = ls 'C:\Scripts\Replace-RDP\TESTFILES\' -Include *.rdp -Recurse -Force -ErrorAction SilentlyContinue | foreach fullname
$Path | (Get-Content -Path $Path) -Replace 'IPserver1','newIPserver1' -Replace 'IPserver2','newIPserver2' | Set-Content $Path -Force
Have found most of the solution with Powershell but have a problem with the results. The second line of code when output to the screen changes contents correctly in memory. The content written to file however resulted in the new server IP address being written into ALL rdp files even if the source rdp file's target IP address doesn't match the -Replace criterion.
Text inside a .rdp on the relevant line is:
full address:s:192.168.1.123
changes to:
full address:s:172.16.1.23
Thank you for all assistance in reaching the endpoint. Have spent hours learning from various sites and code snippets.
You need to keep track of each file that you are reading so that you can save changes to that file. Foreach-Object makes this process easy. Inside of the Foreach-Object script block, the current object $_ is the FullName value for each of your files.
$CurrentIP1 = '192\.168\.1\.123'
$CurrentIP2 = '192\.168\.1\.124'
$NewIP1 = '172.16.1.23'
$NewIP2 = '172.16.1.24'
$files = (Get-ChildItem 'C:\Scripts\Replace-RDP\TESTFILES\' -Filter *.rdp -Recurse -Force -File -ErrorAction SilentlyContinue).FullName
$files | Foreach-Object {
if (($contents = Get-Content $_) -match "$CurrentIP1|$CurrentIP2") {
$contents -replace $CurrentIP1,$NewIP1 -replace $CurrentIP2,$NewIP2 |
Set-Content $_
}
}
Note that using the -File switch on Get-ChildItem (alias ls) outputs only files. Since -replace uses regex to do matching, you must backslash escape literal . characters.

How to display the file a match was found in using get-content and select-string one liner

I am attempting to search a directory of perl scripts and compile a list of all the other perl scripts executed from those files(intentionally trying to do this through Powershell). A simplistic dependency mapper, more or less.
With the below line of code I get output of every line where a reference to a perl file is found, but what I really need is same output AND the file in which each match was found.
Get-Content -Path "*.pl" | Select-String -Pattern '\w+\.pl' | foreach {Write-Host "$_"}
I have succeeded using some more complicated code but I think I can simplify it and accomplish most of the work through a couple lines of code(The code above accomplishes half of that).
Running this on a windows 10 machine powershell v5.1
I do things like this all the time. You don't need to use get-content.
ls -r *.pl | Select-String \w+\.pl
file.pl:1:file2.pl
You don't need to use ls or Get-ChildItem either; Select-String can take a path parameter:
Select-String -Pattern '\w+\.pl' -Path *.pl
which shortens to this in the shell:
sls \w+\.pl *.pl
(if your regex is more complex it might need spaces around it).
For the foreach {write-host part, you're writing a lot of code to turn useful objects back into less-useful strings, and forcibly writing them to the host instead of the standard output stream. You can pick out the data you want with:
sls \w+\.pl *.pl | select filename, {$_.matches[0]}
which will keep them as objects with properties, but render by default as a table.