Trying to simplify Powershell Code to Single Line - powershell

The following snippet of code gets a list of paths, converts each character of each path to its ASCII value and stores this in a variable named $output
$itemlist = (Get-ChildItem -Recurse -Path "$data_path").FullName
Foreach($item in $itemlist)
{
$output+= [int[]]#("$item").ToCharArray() -join ',' | Out-String
}
Write-Host $output
I'm trying to get all this in a single line (due to a complicated reason that prevents me from running a Powershell script) so I can run a single Powershell command to get the same effect but so far I'm having a hard time.
Given below is what I've managed to achieve.
(Get-ChildItem -Recurse -Path "$data_path").FullName | Format-Table -Property #{n='';e={[int[]]#($_).ToCharArray() -join ','}} | Out-Host
The output however doesn't have the ASCII values, the paths in the output are represented in characters. Can someone please help me out with this ?

How you should be doing it if you're running this from cmd.exe:
$Command = #'
$itemlist = (Get-ChildItem -Recurse -Path "$data_path").FullName
Foreach($item in $itemlist)
{
$output+= [int[]]#("$item").ToCharArray() -join ',' | Out-String
}
Write-Host $output
'#
[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Command))
End result:
powershell.exe -NoProfile -NoLogo -EncodedCommand JABpAHQAZQBtAGwAaQBzAHQAIAA9ACAAKABHAGUAdAAtAEMAaABpAGwAZABJAHQAZQBtACAALQBSAGUAYwB1AHIAcwBlACAALQBQAGEAdABoACAAIgAkAGQAYQB0AGEAXwBwAGEAdABoACIAKQAuAEYAdQBsAGwATgBhAG0AZQAgAAoAIABGAG8AcgBlAGEAYwBoACgAJABpAHQAZQBtACAAaQBuACAAJABpAHQAZQBtAGwAaQBzAHQAKQAKACAAIAAgACAAewAgAAoAIAAgACAAIAAgACQAbwB1AHQAcAB1AHQAKwA9ACAAWwBpAG4AdABbAF0AXQBAACgAIgAkAGkAdABlAG0AIgApAC4AVABvAEMAaABhAHIAQQByAHIAYQB5ACgAKQAgAC0AagBvAGkAbgAgACcALAAnACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcACgAgACAAIAAgAH0ACgAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAkAG8AdQB0AHAAdQB0AA==

If this is turning into a short contest..
ls $data_path -r|%{($_|% f*|% t*y|% toi*2 $null)-join','}|out-string

I guess you can use this:
(Get-ChildItem -Recurse -Path "$data_path").FullName | ForEach-Object { [int[]]#("$_").ToCharArray() -join ',' } | Out-String

$itemlist = (Get-ChildItem -Recurse -Path "$data_path").FullName; Foreach($item in $itemlist){$output+= [int[]]#("$item").ToCharArray() -join ',' | Out-String};Write-Host $output

Get-ChildItem -Path "$data_path" | ForEach-Object { [int[]][char[]]$_.FullName -join ',' }
Get-ChildItem -Path "$data_path" | ForEach-Object { [int[]]#($_.FullName).ToCharArray() -join ',' }

Using aliases is all I can think of to shorten it further than anyone has so far:
ls "$data_path" -r | %{[int[]]#($_.FullName).ToCharArray() -join ","} | Out-String

$itemlist = (Get-ChildItem -Recurse -Path "$data_path").FullName
Foreach($item in $itemlist)
{
$output+= [int[]]#("$item").ToCharArray() -join ',' | Out-String
}
Write-Host $output

Related

Powershell 5: ConvertTo-Csv a CSV with quotes in some but not all columns

I am building am updating a script which imports a large CSV file and then splits it into lots of separate CSV files based on the value in the first two columns
so POIMP_NL_20210306.csv which contains:
DOC_NUMBER|COMMENTS|ITEM|QTY|SUPPLIER
P-100-1234|JANE|5059585896978|2|"JOES SUPPLIES"
P-100-1234|JANE|5059585896985|2|"JOES SUPPLIES"
P-100-6666|TED|5059585896992|1|"ACTION TOYS"
must be split into POIMP_P-100-1234_JANE.csv containing
P-100-1234|JANE|5059585896978|2|"JOES SUPPLIES"
P-100-1234|JANE|5059585896985|2|"JOES SUPPLIES"
and POIMP_P-100-6666_TED.csv
P-100-6666|TED|5059585896992|1|"ACTION TOYS"
The problem I am trying to solve is preserving the quotes in just the SUPPLIER column
Since ConvertTo-Csv adds quotes to everything, I use a % { $_ -replace '"', ""} to remove these all before the out-file is created but of course it removes these from the SUPPLIER column 2
Here is my script which perfectly splits the big file into smaller files by DOC_NUMBER and COMMENTS but removes all quotes:
$basePath = "C:\"
$archivePath = "$basePath\archive\"
$todaysDate = $(get-date -Format yyyyMMdd)
$todaysFiles = #(
(Get-ChildItem -Path $basePath | Where-Object { $_.Name -match 'POIMP_' + $todaysDate })
)
cd $basePath
foreach ($file in $todaysFiles ) {
$fileName = $file.ToString()
Import-Csv $fileName -delimiter "|" | Group-Object -Property "DOC_NUMBER","COMMENTS" |
Foreach-Object {
$newName = $_.Name -replace ",","_" -replace " ",""; $path=$fileName.SubString(0,8) + $newName+".csv" ; $_.group |
ConvertTo-Csv -NoTypeInformation -delimiter "|" | % { $_ -replace '"', ""} | out-file $path -fo -en ascii
}
Rename-Item $fileName -NewName ([io.path]::GetFileNameWithoutExtension("$fileName") + "_Original.csv")
Move-Item (Get-ChildItem -Path $basePath | Where-Object { $_.Name -match '_Original' }) $archivePath -force
}
And here is another script which I found online and amended and which successfully leaves quotes in just the SUPPLIER column by first adding double back ticks and then replacing these with quotes after all others have been removed
$ImportedCSV = Import-CSV "C:\POIMP_NL_20210306.csv" -delimiter "|"
$NewCSV = Foreach ($Entry in $ImportedCsv) {
$Entry.SUPPLIER = '¬¬' + $Entry.SUPPLIER + '¬¬'
$Entry
}
$NewCSV |
ConvertTo-Csv -NoTypeInformation -delimiter "|" | % { $_ -replace '"', ""} | % { $_ -replace '¬¬', '"'} | out-file "C:\updatedPO.csv" -fo -en ascii
I just can't merge these scripts to achieve the desired result as I can't seem to reference the correct object. I'd really appreciate your help! Thanks
Any good CSV reader should be able to handle quotes around csv fields, even when not really needed.
Having said that, It is your explicit wish to only have quotes around the field in the SUPPLIER column. (Note, in your example there is a trailing space after that column name)
In this case, I think this would help.
Not only does it surround the SUPPLIER fields with quotes, but also saves the data as separate files using the values from column DOC_NUMBER and COMMENTS per group found in the csv
$path = 'D:\Test'
$fileIn = Join-Path -Path $path -ChildPath 'POIMP_NL_20210306.csv'
# import the csv file and group first two columns
Import-Csv -Path $fileIn -Delimiter '|' | Group-Object -Property "DOC_NUMBER","COMMENTS" | ForEach-Object {
$headerDone = $false
$data = foreach ($item in $_.Group) {
if (!$headerDone) {
$item.PsObject.Properties.Name -join '|'
$headerDone = $true
}
$item.SUPPLIER = '"{0}"' -f $item.SUPPLIER
$item.PsObject.Properties.Value -join '|'
}
# create a new filename like 'POIMP_P-100-1234_JANE.csv'
$fileOut = Join-Path -Path $path -ChildPath ('POIMP_{0}_{1}.csv' -f $_.Group[0].DOC_NUMBER, $_.Group[0].COMMENTS)
# save the data not using Export-Csv because that will add quotes around everything (in PowerShell 5)
$data | Set-Content -Path $fileOut -Force
}
Output
POIMP_P-100-1234_JANE.csv
DOC_NUMBER|COMMENTS|ITEM|QTY|SUPPLIER
P-100-1234|JANE|5059585896978|2|"JOES SUPPLIES"
P-100-1234|JANE|5059585896985|2|"JOES SUPPLIES"
POIMP_P-100-6666_TED.csv
DOC_NUMBER|COMMENTS|ITEM|QTY|SUPPLIER
P-100-6666|TED|5059585896992|1|"ACTION TOYS"
If you are Powershell 7 or later, you can use
$yourdata | ConvertTo-Csv -NoTypeInformation -QuoteFields "SUPPLIER" -Delimiter "|" |
Out-File ...
or you could use
$yourdata | Export-Csv -NoTypeInformation -QuoteFields "SUPPLIER" `
-Delimiter "|" -Path <path-to-output-file>.csv
You can also use -UseQuotes AsNeeded to let the converter add quoting where it thinks it makes sense, otherwise just specify the fields you want quoted.

Powershell script to automate gci

I'm tying to automate gci in order to work on each row in a config file, where for each row I have as first column the path, and following it a list of files. Something like this:
C:\Users\*\AppData\Roaming\* *.dll
C:\Test file.txt,file2.txt
This means that gci will search for:
*.dll in C:\Users*\AppData\Roaming*
file.txt in C:\Test
file2.txt in C:\Test
In order to do this I'm creating dynamically the where condition in the script below. Here the ps script I'm using
foreach($line in Get-Content .\List.txt) {
try {
$path,$files = $line.split(' ')
$files = $files.split(',')
}
catch {
$path = $line
$files = "*.*"
}
if([string]::IsNullOrEmpty($files)){
$files = "*.*"
}
$filter = $files -join(" -or `$_.Name` -like ")
$filter = "`$_.Name` -like " + $filter
echo "Searching Path: $path, Pattern: $filter" | out-file -append -encoding ASCII -filepath .\result.txt
if ($path.Contains("*"))
{
gci -Path $path -Recurse | Where {$filter} | Select -ExpandProperty FullName | Out-String -Width 2048 | out-file -append -encoding UTF8 -filepath .\result.txt
}
else
{
gci -Path $path | Where {$filter} | Select -ExpandProperty FullName | Out-String -Width 2048 | out-file -append -encoding UTF8 -filepath .\result.txt
}
}
The problem is that the where filter is not considered. All files are returned
First attempt, suggested by
foreach($line in Get-Content .\List.txt) {
try {
$path,$files = $line.split(' ')
$files = $files.split(',')
}
catch {
$path = $line
$files = "*.*"
}
if([string]::IsNullOrEmpty($files)){
$files = "*.*"
}
$filter = $files -join(" -or `$_.Name -like ")
$filter = "`$_.Name -like " + $filter
$gciParams = #{
Path = $Path
Recurse = $Path.Contains('*')
}
"Searching Path: $path, Pattern(s): [$($files -join ',')]" | Add-Content -Path .\result.txt -Encoding ASCII
Get-ChildItem #gciParams | Where $filter | Select -ExpandProperty FullName | Add-Content -Path .\result.txt -Encoding UTF8
}
If you want to create a piece of code and defer execution of it until later, you need a Script Block.
A Script Block literal in PowerShell is just {}, so for constructing script block to filter based on a single comparison, you'd want to define $filter like this:
$filter = {$_.Name -like $filter}
At which point you can pass it directly as an argument to Where-Object:
Get-ChildItem $path |Where-Object $filter
... but since you want to test against multiple wildcard patterns, we'll need to write a slightly different filtering routine:
$filter = {
# Store file name of file we're filtering
$FileName = $_.Name
# Test ALL the patterns in $files and see if at least 1 matches
$files.Where({$FileName -like $_}, 'First').Count -eq 1
}
Since the $filter block now references $files to get the patterns, we can simplify your loop as:
foreach($line in Get-Content .\List.txt) {
try {
$path,$files = $line.split(' ')
$files = $files.split(',')
}
catch {
$path = $line
$files = "*.*"
}
if([string]::IsNullOrEmpty($files)){
$files = "*.*"
}
$gciParams = #{
Path = $Path
Recurse = $Path.Contains('*')
}
"Searching Path: $path, Pattern(s): [$($files -join ',')]" | Add-Content -Path .\result.txt -Encoding ASCII
Get-ChildItem #gciParams | Where $filter | Select -ExpandProperty FullName | Add-Content -Path .\result.txt -Encoding UTF8
}
Note that we no longer need to re-define $filter everytime the loop runs - the condition is based on the value of $files at runtime, so you can define $filter once before entering the loop and then reuse $filter every time.
The "trick" with using #gciParams (which allows us to remove the big if/else block) is known as splatting, but you could achieve the same result with Get-ChildItem -Path:$Path -Recurse:$Path.Contains('*') :)

why am I having this error while using replace methode

I am trying to list first and second level folders of a path. the script works fine, but I am having this error "You cannot call a method on a null-valued expression." any idea why ?
$folderPath = '\\FILSERVER\DATA$'
$PathScript = "C:\Users\adm\Desktop\Script_V.2"
$sites = "Madrid"
foreach ($site in $Sites){
#Get_Level_1_Folders
$PathShare = "\\FILSERVER\DATA$\Data_$site"
Get-ChildItem -Path $PathShare -Directory -Force -ErrorAction SilentlyContinue | Select-Object FullName | out-file "${PathScript}\level_1_${site}.txt"
(get-content "${PathScript}\level_1_${site}.txt") -notmatch "--------" | out-file "${PathScript}\level_1_${site}.txt"
(get-content "${PathScript}\level_1_${site}.txt").replace("\\FILSERVER\DATA$\Data_$site\","" ) | out-file "${PathScript}\level_1_${site}.txt"
(get-content "${PathScript}\level_1_${site}.txt") -notmatch "FullName" | out-file "${PathScript}\level_1_${site}.txt"
(get-content "${PathScript}\level_1_${site}.txt") | Foreach {$_.TrimEnd()} | Set-Content "${PathScript}\level_1_${site}.txt"
(get-content "${PathScript}\level_1_${site}.txt") | ? {$_.trim() -ne "" } | set-content "${PathScript}\level_1_${site}.txt"
#Get_Level_2_Folders
$Level_Folders = get-content "${PathScript}\level_1_${site}.txt"
foreach($lv1 in $Leve1_Folders){
Get-ChildItem -Path $PathShare\$lv1 -Directory -Force -ErrorAction SilentlyContinue | Select-Object FullName | out-file "${PathScript}\level_2_${site}_${lv1}.txt"
(get-content "${PathScript}\level_2_${site}_${lv1}.txt") -notmatch "--------" | out-file "${PathScript}\level_2_${site}_${lv1}.txt"
(get-content "${PathScript}\level_2_${site}_${lv1}.txt").replace("\\FILSERVER\DATA$\Data_$site\","") | out-file "${PathScript}\level_2_${site}_${lv1}.txt"
(get-content "${PathScript}\level_2_${site}_${lv1}.txt") -notmatch "FullName" | out-file "${PathScript}\level_2_${site}_${lv1}.txt"
(get-content "${PathScript}\level_2_${site}_${lv1}.txt") | Foreach {$_.TrimEnd()} | Set-Content "${PathScript}\level_2_${site}_${lv1}.txt"
(get-content "${PathScript}\level_2_${site}_${lv1}.txt") | ? {$_.trim() -ne "" } | set-content "${PathScript}\level_2_${site}_${lv1}.txt"
}
As mentioned in comments, the cause is likely that this expandable string:
"${PathScript}\level_2_${site}_${lv1}.txt"
... resolved to the path of a file that's empty.
Get-Content will open the file - which is why you don't get any "file not found" errors - and then immediately return without outputting anything, since there's no meaningful "lines" to consume in an empty file.
The result of the (Get-Content ...) expression is therefore $null, and you received the error in question.
You can either use the -replace operator which will take any number of strings (including none) as input - just make sure you escape the arguments:
(Get-Content "${PathScript}\level_2_${site}_${lv1}.txt") -replace [regex]::Escape("\\FILSERVER\DATA$\Data_$site\") |Out-File ...
Or let the pipeline take care of enumerating the output instead of relying on implicit property enumeration:
Get-Content "${PathScript}\level_2_${site}_${lv1}.txt" |ForEach-Object {$_.Replace("\\FILSERVER\DATA$\Data_$site\","")} |Out-File ...

Export-CSV with the output path as a variable

Is there a way I can export the list to a text file and separate them by share name somehow, not single file?
I'd like to do in this format, "$hostname-$sharename.txt".
Here is what I have so far:
$Shares = Get-WmiObject Win32_Share -Filter "not name like '%$'" |
Select-Object -Expand Path
$re = ($Shares | ForEach-Object {[Regex]::Escape($_)}) -join '|'
$results = foreach ($Share in $Shares) {
(Get-ChildItem $Share -Recurse | Select-Object -Expand FullName) -replace "^($re)\\"
}
$results | Out-File -FilePath "C:\Output\$($env:computername)-$sharename.txt"
If I understand the question correctly, all you are missing in your code is the sharename part for the various output files.
Below will hopefully do what you want:
$outputDir = 'C:\Output'
$Shares = Get-WmiObject Win32_Share -Filter "not name like '%$'"
$re = ($Shares | ForEach-Object {[Regex]::Escape($_.Path)}) -join '|'
foreach ($Share in $Shares) {
$result = (Get-ChildItem -Path $Share.Path -File -Recurse | Select-Object -Expand FullName) -replace "^($re)\\"
# output the results per share in a text file
$fileOut = Join-Path -Path $outputDir -ChildPath ('{0}-{1}.txt' -f $env:COMPUTERNAME, $Share.Name)
$result | Out-File -FilePath $fileOut -Force
}

Filter lines according to word by powershell

I want to filter lines according to specific word from file in powershell.
For example: the files animal1.txt and animal2.txt. Every file contain lines
dog
cat
dog
dog
bird
Then I want to create two derived files:
animal1_bak.txt that stores lines which contains the word 'dog' from animal1.txt
animal2_bak.txt that stores lines which contains the word 'dog' from animal2.txt
What I found on web is:
Select-String -Path "*.*" -Pattern "dog"
But the instruction to create the derived word is missing.
What can I do?
You can first get-content and use set-content like below
Get-Content -Path E:\KTDocs\Scripts\animal1.txt | where {
$_ -like '*dog*'} |Set-Content e:\animalbak.txt
try Something like this
select-string -Path "c:\temp\animal*.txt" -Pattern "dog" | Group Path | %{
$FileName="{0}_bak.txt" -f $_.Name
$_.Group.Line | select -unique | Out-File $FileName -Append
}
$folderpath = "D:\AnimalFolder" # your folder path here
$Allfiles = Get-ChildItem -Path $folderpath -Recurse -File -Force -ErrorAction SilentlyContinue |where{$_.Name -match ".txt"} |Select-Object -expandproperty FullName
foreach($filepath in $allfiles)
{
$Data = get-content $filepath
foreach($line in $data)
{
if($line -match "dog")
{
$newpath = $filepath.split('.')[0]
$getfullpath = $newpath + "_bak.txt"
$line | out-file $getfullpath -append
}
}
}