My use of Out-File and/or [system.io.file]::WriteAllText - powershell

Both commands work when running inside Visual Studio Code but if I execute the ps1 file directly in pwsh 7 the file is blank. Same if I right-click and select Open With pwsh.
Tried both commands, out-file first but moved to [system.io.file]::WriteAllText to remove blank rows. Both successfully create and populate a csv file as expected when I execute it from within Visual Studio Code.
try {
$files = #()
$files = Get-ChildItem -Path "\\networkShare\ps1files"
#$files = $files + (get-childitem -Path "\\networkShare\ps1files2")
$results = foreach ($file in $files) {
if ($file.FullName.substring($file.FullName.length - 3, 3) -eq "ps1") {
#Write-Host $file
$scriptblock = [scriptblock]::Create((Get-Content -Raw -Path $file.FullName))
$ast = $scriptblock.Ast
$commands = $ast.FindAll( { $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true)
$commandText = foreach ($command in $commands) {
$command.CommandElements[0].Extent.Text
}
$commandText |
Select-Object -Unique |
Sort-Object |
Select-Object #{
Label = "Module"
Expression = { (Get-Command $_).Source.Substring(0, (Get-Command $_).Source.Length) }
}
$value = ""
foreach ($result in $results) {
if ($result.Module -inotlike "C:*") {
if ($value -inotlike "*" + $result.Module + "*") {
$value = $value + $result.Module + "`n"
}
}
}
}
}
$ModulesInUse = #()
$NewModules = #()
$NewestMod = #()
#$NewestMod = ""
$ModulesInUse = Import-Csv \\networkShare\PSModulesUsed.csv -Header "Module"
$NewModules = $results.Where({ $_.module -notin $ModulesInUse.Module -and $_.Module.Length -gt 0 -and $_.Module -inotlike "C:*" })
if ($NewModules.count -gt 0) {
Write-Host "New Modules found `n"
# Create unique array of new modules found
foreach ( $mod in $NewModules ) {
if ($mod.Module -notin $NewestMod.Module) {
$NewestMod = $NewestMod + $mod
}
}
$NewestMod.module
Write-Host `n
$Prompt = 'Do you want to update the Modules Used csv file now? Y/N'
$UpdateFile = Read-Host -Prompt $Prompt
if ($UpdateFile -eq 'y') {
#$value | Out-File "\\networkShare\PSModulesUsed.csv"
[system.io.file]::WriteAllText(“\\networkShare\PSModulesUsed.csv”, $value)
}
}
else {
Read-Host "No new Modules found. Press any key to exit"
}

Big thanks to mklement0!!!
Making that change to Code revealed the $value I was sending was not correct, even though it was working before. When running outside of Code it was blank.
I moved the code below to outside the $Results block of code and it works now.
$value = ""
foreach ($result in $results) {
if ($result.Module -inotlike "C:*") {
if ($value -inotlike "*" + $result.Module + "*") {
$value = $value + $result.Module + "`n"
}
}
}

Related

Find and Replace multi-line string

I'm trying to find a string and add the string needed for a program.
I need the code to look to see if the action = run fast already exists and if so do nothing.
$Input = GetContent "${Env:ProgramFiles}\myprogram\file.conf"
$replace = #"
[MyAction_Log]
action = run fast
"#
$Input -replace ('action = run fast') -replace ('\[MyAction_Log\]',$replace) | set-content "${Env:ProgramFiles}\myprogram\file.conf"
I would check before wantonly replacing things you think exist. Also, never use $Input as a variable name; it's an automatic variable and won't do what you think it will (treat it as read-only).
$path = "$Env:ProgramFiles\prog\file.conf"
$file = Get-Content -Path $path
$replacementString = #'
[MyAction_Log]
action = run fast
'#
if ($file -notmatch 'action\s=\srun\sfast')
{
$file -replace '\[MyAction_Log\]', $replacementString |
Set-Content -Path $path
}
An alternative that's able to cope with the action key being located anywhere in the [MyAction_Log] section
$Inside = $False
$Modified = $False
$Path = "$( $env:ProgramFiles )\prog\file.conf"
$NewLines = Get-Content $Path |
ForEach-Object {
if( $_.Trim() -like "[*]" ) { $Inside = $False }
if( $_.Trim() -like "*[MyAction_Log]*" ) { $Inside = $True }
If( $Inside -and $_ -like "action = *" -and $_ -notlike "*run fast*" ) { $Modified = $True; "action = run fast" } else { $_ }
}
If( $Modified ) { $NewLines | Set-Content $Path }

How to Return Value to use in another function powershell

In the below code I am having trouble understanding why when I return FileArray why my other function is returning it as being empty?
I am looking to use copyfiles with FileArray. Am I just supposed to put all the steps into one huge function?
function Import-Excel
{
param (
[string]$FileName,
[string]$WorksheetName,
[bool]$DisplayProgress = $true
)
if ($FileName -eq "") {
throw "Please provide path to the Excel file"
Exit
}
if (-not (Test-Path $FileName)) {
throw "Path '$FileName' does not exist."
exit
}
$FileName = Resolve-Path $FileName
$excel = New-Object -com "Excel.Application"
$excel.Visible = $false
$workbook = $excel.workbooks.open($FileName)
if (-not $WorksheetName) {
Write-Warning "Defaulting to the first worksheet in workbook."
$sheet = $workbook.ActiveSheet
} else {
$sheet = $workbook.Sheets.Item($WorksheetName)
}
if (-not $sheet)
{
throw "Unable to open worksheet $WorksheetName"
exit
}
$sheetName = $sheet.Name
$columns = $sheet.UsedRange.Columns.Count
$lines = $sheet.UsedRange.Rows.Count
Write-Warning "Worksheet $sheetName contains $columns columns and $lines lines of data"
$fields = #()
for ($column = 1; $column -le $columns; $column ++) {
$fieldName = $sheet.Cells.Item.Invoke(1, $column).Value2
if ($fieldName -eq $null) {
$fieldName = "Column" + $column.ToString()
}
$fields += $fieldName
}
$line = 2
for ($line = 2; $line -le $lines; $line ++) {
$values = New-Object object[] $columns
for ($column = 1; $column -le $columns; $column++) {
$values[$column - 1] = $sheet.Cells.Item.Invoke($line, $column).Value2
}
$row = New-Object psobject
$fields | foreach-object -begin {$i = 0} -process {
$row | Add-Member -MemberType noteproperty -Name $fields[$i] -Value $values[$i]; $i++
}
$row
$percents = [math]::round((($line/$lines) * 100), 0)
if ($DisplayProgress) {
Write-Progress -Activity:"Importing from Excel file $FileName" -Status:"Imported $line of total $lines lines ($percents%)" -PercentComplete:$percents
}
}
$workbook.Close()
$excel.Quit()
}
function FindFiles {
param(
[string]$fiestore
)
$length = $filestore.Length
$GuidArray = #()
for($line=0;$line -le $filestore.Count;$line++){
$check = $filestore[$line]
$length2 = $check.Length
echo $check
$fileGuid = $check | ForEach-Object{$_.FileGuid}
$GuidArray = $GuidArray + $fileGuid
}
write-host "-------------------------------------------------------------" -ForegroundColor Yellow
$filepath = Read-Host " Please Enter File Path to Search"
for ($counter=0;$counter -lt $GuidArray.Count;$counter++){
$fileArray = #()
$guidcheck = $GuidArray[$counter]
echo $guidcheck
$file = Get-ChildItem -Recurse -Force $filePath -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -like "*$guidcheck*") } | Select-Object Directory,Name| Format-Table -AutoSize
$fileArray += $file
}
echo $fileArray
return $fileArray
}
function CopyFiles {
param(
[string]$filearray
)
echo $fileArray
for($counter = 0;$counter -lt $filearrray.Count;$counter++){
echo $filearray[$counter]
#Copy-Item
}
}
function execute {
$filestore = Import-Excel 'C:\594 Sample of Filestore.xlsx'
$fileArray = #()
FindFiles($filestore)
echo $fileArray
CopyFiles($fileArray)
}
$fileArray doesn't become available outside of the Function by doing Return, but you could make it accessible outside of the function by defining it with a Global scope (although this is not best practice): Return $Global:fileArray.
Instead, it becomes the value of the Function call itself, so in Execute you could do:
$filestore = Import-Excel 'C:\594 Sample of Filestore.xlsx'
$fileArray = #(FindFiles($filestore))
echo $fileArray
CopyFiles($fileArray)
However I think you also need to remove any echo statements from within the FindFiles function or they may be returned as well.
Note: This is untested code.

parsing script never ends

i have the following script but it does never end executing.
what could be the issue ? I tried to debug it but apparently it works correctly with a single file, but when I throw it against a folder full of content fails.
$path = split-path -parent $MyInvocation.MyCommand.Definition
$files = Get-ChildItem "$path\CodeForCertification\5_SourceCode\*" -Include *.c,*.h -Recurse | where{
! $_.PSIsContainer
}#$PSScriptRoot
ForEach ($file in $files){
$data = Get-Content -Path $file.FullName
$feature = Get-Content "$path\Disabled_Features.txt"
#[System.ArrayList]$Modifier
$nl=[Environment]::NewLine
$Modifier=#()
$flag=0
$data = $data | ForEach-Object -Begin {
$ignore = $false; $levels = 0
} -Process {
for($counter=0; $counter -lt $feature.Count; $counter++){
$parse = $feature[$counter]
if($_ -match "^#ifdef $parse" -And $flag -eq '0') {
$ignore = $true
$flag = 1;
}
}
if($ignore) {
if ($_ -match "^#ifdef") {
$levels++
}elseif ($_ -match "#endif") {
if($levels -ge 1) {
$levels--
if($levels -eq '0'){
$ignore = $false
}
}
}
}else {
$flag=0
$temp=$_
$_
$Modifier+="$temp"
}
}
$data | Out-File $file.FullName
}
OK, Jackson, let's solve your problem before you enter some kind of question spam filter ;-)
Consider this (just put it somewhere at the start of your script):
function RemoveUndesiredFeatures([string[]]$lines,[string[]]$undesiredFeatures)
{
$inIgnoreBlock = $false
$nestingLevel = 0
foreach ($line in $lines)
{
if ($inIgnoreBlock)
{
# Only search for nested blocks and end of block
if ($line -like "#ifdef*")
{
$nestingLevel++
}
elseif ($line -like "#endif*")
{
$nestingLevel--
}
if ($nestingLevel -eq 0)
{
$inIgnoreBlock = $false
}
}
else
{
# Search for undesired feature
$isIfdefMatch = $line -match "#ifdef (?<feature>\w+)"
if ($isIfdefMatch -and ($Matches.feature -in $undesiredFeatures))
{
# Ignore Feature
$inIgnoreBlock = $true
$nestingLevel++
}
else
{
# Output line
$line
}
}
}
}
Here is my example to use it:
$undesiredFeatures = #("F1","F2") # Just as example. Get-Content on a file with features is also fine
$files = Get-ChildItem *.c,*.h -Recurse # Again, just as example
foreach ($file in $files)
{
$lines = Get-Content $file.FullName
$changedLines = RemoveUndesiredFeatures $lines $undesiredFeatures
if ($changedLines.Count -ne $lines.Count)
{
# Features were removed. Write out changed file (to a different file to preserve my test files)
Set-Content -Value $changedLines -Path "$($file.FullName).changed"
}
}

Retrieve matching strings from two text files

I have a text file file_paths.txt that contains full paths on each line:
C:\MyFolder1\app1.exe
C:\MyFolder2\l1.dll
C:\MyFolder3\app2.exe
C:\MyFolder1\l2.dll
C:\MyFolder5\app3.exe
C:\MyFolder3\app4.exe
C:\MyFolder6\app5.exe
I also have file folders.txt that contains list of folders:
C:\MyFolder1
C:\MyFolder2
C:\MyFolder3
C:\MyFolder4
C:\MyFolder8
I need to iterate through the list of folders in folders.txt, match it with files in file_paths.txt and write the results to a file result.txt like this:
In C:\MyFolder1 more than one files has been found:
C:\MyFolder1\app1.exe
C:\MyFolder1\l2.dll
In C:\MyFolder2 one file has been:
C:\MyFolder2\l1.dll
In C:\MyFolder3 more than one files has been found:
C:\MyFolder3\app2.exe
C:\MyFolder3\app4.exe
In C:\MyFolder4 no files has been found.
In C:\MyFolder8 no files has been found.
My attempt that doesn't work:
$paths = [System.IO.File]::OpenText("file_paths.txt")
$folders = [System.IO.File]::OpenText("folders.txt")
$result = "result.txt"
try {
for(;;) {
$folder = $folders.ReadLine()
if ($folder -eq $null) { break }
"In ">> $folder >> ": `n" >> $result
for(;;) {
$path = $paths.ReadLine()
if ($path -eq $null) { break }
if ($path -contains $folder) {" ">>$path>>"`n">>$result }
}
}
} finally {
$paths.Close()
$folders.Close()
}
I would separate processing from reporting. First build a hashtable from the contents of folders.txt and add the lines from file_paths.txt to the matching keys:
$folders = #{}
Get-Content 'folders.txt' | ForEach-Object { $folders[$_] = #() }
Get-Content 'file_paths.txt' | ForEach-Object {
$line = $_
$($folders.Keys) | Where-Object {
$line -like "$_*"
} | ForEach-Object {
$folders[$_] += $line
}
}
Then you can output the resulting data structure like this:
$folders.Keys | ForEach-Object {
'In {0} {1} files have been found' -f $_, $folders[$_].Count
if ($folders[$_].Count -gt 0) {
$folders[$_] | ForEach-Object { "`t$_" }
}
} | Out-File 'result.txt'
Below is a script you can use to do exactly what you need.
Note the $folderPath and $filePath variables. Replace with absolute or relative (to where you execute the script) path of the file_paths.txt and folders.txt files.
$folderPath = 'folders.txt'
$filePath = 'file_paths.txt'
(Get-Content $folderPath).Split('`r`n') | ForEach-Object {
$folder = $_
$count = 0
$fileArray = #()
(Get-Content $filePath).Split('`r`n') | ForEach-Object {
$file = $_
if( $file | Select-String $folder -Quiet ) {
$count++
$fileArray += $file
}
}
if($count -ne 0) {
Write-Output "In $folder, $count files has been found."
$fileArray | ForEach-Object {
Write-Output "`t$_"
}
} else {
Write-Output "In $folder, no files has been found."
}
}

powershell edit file write in the middle

I want to create a powershell script that will edit a config file (apache httpd.conf) and if I find
#keyword
some data
#keyword
the I replace the data between the #keyword else I add the #keywords with the data.
Can someone give me some help
Thank you
$fileName = "..\..\apache\conf\extra\httpd-vhosts.conf"
$found = $false
$contains = $false
$kw = "##xceed.localhost"
$text = "A
BBBB
C"
(Get-Content ( $fileName )) |
Foreach-Object{
if($_ -match $kw)
{
if($found -eq $false)
{
$found=$true
}else
{
$found=$false
}
if($found -eq $true)
{
$contains=$true
#Add Lines after the selected pattern
$kw
$text
}
}
if($found -ne $true)
{
$_
}
} | Set-Content( $fileName )
if($contains -eq $false)
{
$value=($kw+"`r`n"+$text+"`r`n"+$kw)
Add-Content $fileName $value
}