I want to do the following:
I need to check the content of a (text) file. If a defined string is not there, it has to be inserted on a specific position.
I.e.:
My textfile is a configuration file with different sections, example:
[default]
name=bob
alias=alice
foo=bar
example=value
[conf]
name=value
etc=pp
I want to check if the string “foo=bar” and “example=value” exists in this file. If not, it has to get inserted, but I can't just append the new lines, since they have to be in the certain (here [default]) section and not to the end of the file. The position within the section doesn’t matter.
I tried with the following PowerShell script, which actually just looks for a definitely existing string and adds the new lines after it. Therefore I can make sure that the new lines get inserted in the right section, but I can't make sure that they won't be doubled, since my script doesn't check if they already exist.
$InputFile = "C:\Program Files (x86)\Path\to\file.ini"
$find = [regex]::Escape("alias=alice")
$addcontent1 = "foo=bar"
$addcontent2 = " example=value `n"
$InputFileData = Get-Content $InputFile
$matchedLineNumber = $InputFileData |
Where-Object{$_ -match $find} |
Select-Object -Expand ReadCount
$InputFileData | ForEach-Object{
$_
if ($_.ReadCount -eq ($matchedLineNumber)) {
$addcontent1
$addcontent2
}
} | Set-Content $InputFile
As mentioned by Bill_Stewart, Ansgar Wiechers and LotPings there are multiple modules to work with .ini files available in the web.
Let's take this one for example. Once you download it and import you can see how it imports your file (I removed foo=bar to demonstrate):
PS C:\SO\51291727> $content = Get-IniContent .\file.ini
PS C:\SO\51291727> $content
Name Value
---- -----
default {name, alias, example}
conf {name, etc}
From here what you want to do is pretty simple - check if key exists and if not - add:
if ($content.default.foo -ne 'bar') {
$content.default.foo='bar'
}
Verify that the value has been inserted:
PS C:\SO\51291727> $content.default
Name Value
---- -----
name bob
alias alice
example value
foo bar
And export:
$content | Out-IniFile .\out.ini
Related
I want to verify the database for the given script in CI.
I want to check specific string exists or not in the given file.
Below is the content of my file
USE [SampleDB]
I am reading the content of my file and checking [SampleDB] string exists or not in the file.
I have tried the below things.
If(Get-Content -path D:/Document/admin.sql | Select-String -Pattern "[SampleDB]")
{
# Right Database exist
} else {
# Right Database does not exist
}
How can I verify the right database name after the USE string?
Select-String uses regular expressions to perform string searches, and the construct [...] has a special meaning in that context - namely, it means "match any one of these characters", not quite what you're expecting.
To search for the word [SampleDB] verbatim you either have to escape it correctly:
# escape your search term!
$searchTerm = [regex]::Escape('[SampleDB]')
if(Get-Content -path D:/Document/admin.sql | Select-String -Pattern $searchTerm) {
# Right Database exist
} else {
# Right Database does not exist
}
... or you can request Select-String perform a non-regex match with the -SimpleMatch switch:
if(Get-Content -path D:/Document/admin.sql | Select-String -Pattern "[SampleDB]" -SimpleMatch) {
# Right Database exist
} else {
# Right Database does not exist
}
I have a single file that contains 1GB worth of data. This data is actually 10's of thousands of individual mini files.
I need to extract each individual file and place them in their own separate Distinct file.
So essentially, I need to go from a single file to 30K+ separate files.
Here is a sample of what My file looks like.
FILENAM1 VER 1 32 D
10/15/87 09/29/87
PREPARED BY ?????
REVISED BY ?????
DESCRIPTION USER DOMAIN
RECORD FILENAM1 VER 1 D SUFFIX -4541
100 05 ST-CTY-CDE-FMHA-4541 DISPLAY
200 10 ST-CDE-FMHA-4541 9(2) DISPLAY
300 10 CTY-CDE-FMHA-4541 9(3) DISPLAY
400 05 NME-CTY-4541 X(20) DISPLAY
500 05 LST-UPDTE-DTE-4541 9(06) DISPLAY
600 05 FILLER X DISPLAY 1REPORT NO. 08
DATA DICTIONARY REPORTER REL 17.0 09/23/21
PAGE 2 DREPORT 008
RECORD REPORT
-************************************************************************************************************************************
RECORD RECORD ---- D A T E ----
RECORD NAME LENGTH BUILDER TYPE
OCCURRENCES UPDATED CREATED
************************************************************************************************************************************ 0
FILENAM2 VER 1 176 D
03/09/98 02/21/84
PREPARED BY ??????
REVISED BY ??????
DEFINITION
I Need split the files out based upon a match of VER in position 68, 69 and 70. I also need to name each file uniquely. That information is stored on the same line in position 2-9. In the example above that string is "FILENAM1" and FILENAM2".
So just using the example above I would create two output files and they would be named FILENAM1.txt and FILENAM2.txt.
Since I have 30K+ files I need to split, doing this manually is impossible.
I do have a script that will split a file into multiple files but it will not search for strings by position.
Would anyone be able to assist me with this?
Here is script that DOES NOT Work. Hopefully I can butcher it and get some valid results....
$InputFile = "C:\COPIES.txt"
$Reader = New-Object System.IO.StreamReader($InputFile)
$OPName = #()
While (($Line = $Reader.ReadLine()) -ne $null) {
If ($Line -match "VER"(67,3)) {
$OPName = $Line.(2,8)
$FileName = $OPName[1].Trim()
Write-Host "Found ... $FileName" -foregroundcolor green
$OutputFile = "$FileName.txt"
}
Add-Content $OutputFile $Line
}
Thank you in advance,
-Ron
I suggest using a switch statement, which offers both convenient and fast line-by-line reading of files via -File and regex-matching via -Regex:
$streamWriter = $null
switch -CaseSensitive -Regex -File "C:\COPIES.txt" {
'^.(.{8}).{58}VER' { # Start of a new embedded file.
if ($streamWriter) { $streamWriter.Close() } # Close previous output file.
# Create a new output file.
$fileName = $Matches[1].Trim() + '.txt'
$streamWriter = [System.IO.StreamWriter] (Join-Path $PWD.ProviderPath $fileName)
$streamWriter.WriteLine($_)
}
default { # Write subsequent lines to the same file.
if ($streamWriter) { $streamWriter.WriteLine($_) }
}
}
$streamWriter.Close()
Note: A solution using the .Substring() method of the [string] type is possible too, but would be more verbose.
The ^.(.{8}).{58} portion of the regex matches the first 67 characters on each line, while capturing those in (1-based) columns 2 through 9 (the file name) via capture group (.{8}), which makes the captured text available in index [1] of the automatic $Matches variable. The VER portion of the regex then ensures that the line only matches if VER is found at column position 68.
For efficient output-file creation, [System.IO.StreamWriter] instances are used, which is much faster than line-by-line Add-Content calls. Additionally, with Add-Content you'd have to ensure that a target file doesn't already exist, as the existing content would then be appended to.
My company has deployed Splunk to gather logs, and report system changes.
Splunk uses two files - inputs.conf and server.conf to track the existing host name and report it to Splunk control.
If a host name changes (common in our environment) the system reports a "Broken Host Sanity check" Requiring that we log into each system and replace the old host name, with the new one.
This is difficult with 1000 systems in the field.
I want to automate this process, and I'm having issues with the script I've written (see below).
Both the inputs and server use this format for the .conf files (.conf is just a txt file with a .conf extension).
Host = systemname
The script below will currently read the text file, and then instead of replacing 'systemname' it will append the changes to the end of the existing value. ie. instead of hostname1 ⇔ hostname2 it does Hostname2Hostname1.
$InputsOLD = "host = *"
$InputsNEW = "host = $Env:COMPUTERNAME"
Get-Content "C:\Program Files\SplunkUniversalForwarder\etc\system\local\inputs.conf" |
Foreach-Object {$_ -replace "$InputsOLD","$InputsNEW"} |
Set-Content "C:\Program Files\SplunkUniversalForwarder\etc\system\local\inputs_1.conf"
$ServerOLD = "serverName = *"
$ServerNew = "serverName = $Env:COMPUTERNAME"
Get-Content "C:\Program Files\SplunkUniversalForwarder\etc\system\local\server.conf" |
Foreach-Object {$_ -replace "$ServerOLD","$ServerNew"} |
Set-Content "C:\Program Files\SplunkUniversalForwarder\etc\system\local\server_1.conf"
The -replace operator matches based on a regular expression. So your match expression:
"host = *"
will end its match after it matches its first "host = " not including the host name, and that's what gets replaced, leaving everything intact afterwards. To include the host name in the match expression, use this regular expression:
"host = .*"
I'd use a regular expression which uses a zero length assertion
to match anything on the line following the keyword (even a literal asterisk)
This script changes inplace, using the same file name on save.
## Q:\Test\2019\01\18\SO_54259368.ps1
Push-Location "C:\Program Files\SplunkUniversalForwarder\etc\system\local"
$Clie = "inputs.conf"
$Serv = "server.conf"
(Get-Content $Clie) -replace "(?<=^host = ).*$",$Env:COMPUTERNAME | Set-Content $Clie
(Get-Content $Serv) -replace "(?<=^serverName = ).*$",$Env:COMPUTERNAME | Set-Content $Serv
Pop-Location
I am writing powershell script to find few string in a path. It returns file path correctly in variable. Now if I try to add it to some other variable it adds empty (adds nothing), but if I print variable separately it gets print correctly.
My code,
$final = ""
foreach($e in $stringIDColum)
{
$e
$var = (Get-ChildItem “C:\path” -recurse -exclude $excluded | Select-String -pattern $e | group path | select name)
$final += "," + $e + "," + $var
}
Here value of $e is getting added, $var is not getting added. But if I print $var separately it prints path correctly.
Secondly I have tried various ways, to print line number, but it does not print line number in front of path.
I think the issue is that $var is potentially an array. So you need to convert it a string. Check the type once it's created.
Sorry for the title. I'll try to explain better.
Let's suppose that I run this command in order to get all paths of a directory and I want to redirect them to a text file.
gci e:\mytree -r | % {$_.fullname} | out-file e:\folderstructure.txt
Now I need to recreate this nested structure using new-item cmdlet.
Let's suppose now that I run this command:
gc e:\folderstructure.txt | % {
[system.io.fileinfo]$info = $_
write-host $info $info.extension
}
that produces this output:
E:\mytree\folder1
E:\mytree\folder2
E:\mytree\folder3
E:\mytree\file1.txt .txt
E:\mytree\file12.txt .txt
E:\mytree\folder1\folder.with.dots .dots
E:\mytree\folder1\folder.with.dots\file inside folder with dots.txt .txt
E:\mytree\folder3\file4.doc .doc
As you can see folder.with.dots is a folder but "it's seen" as a file (it gives me .dots extension) because it contains dots within name. If I don't know all possible extensions of my files is there any property that can tell me if an object iscontainer or not so that I can use new-item with the right switch file or directory to create it?
I hope you have understood my problem despite my English. Thanks in advance.
edit. UPDATE after JPBlanc answer
Thank you very much. :)
I was trying in this way:
gc e:\folderstructure.txt | % {
[system.io.directoryinfo]$info = $_
if ($info.psiscontainer) {
write-host "$info is a folder" }
else {
write-host "$info is a file" }
}
and the output was:
E:\mytree\folder1 is a file
E:\mytree\folder2 is a file
E:\mytree\folder3 is a file
E:\mytree\file1.txt is a file
E:\mytree\file12.txt is a file
E:\mytree\folder1\folder.with.dots is a file
E:\mytree\folder1\folder.with.dots\file inside folder with dots.txt is a file
E:\mytree\folder3\file4.doc is a file
Following your advice:
gc e:\folderstructure.txt | % {
[system.io.directoryinfo]$info = $_
if ((get-item $info).psiscontainer) {
write-host "$info is a folder" }
else {
write-host "$info is a file" }
}
E:\mytree\folder1 is a folder
E:\mytree\folder2 is a folder
E:\mytree\folder3 is a folder
E:\mytree\file1.txt is a file
E:\mytree\file12.txt is a file
E:\mytree\folder1\folder.with.dots is a folder
E:\mytree\folder1\folder.with.dots\file inside folder with dots.txt is a file
E:\mytree\folder3\file4.doc is a file
everything works fine. Now I can achieve my goal. Thanks again.
LAST EDIT.
I had another idea. I decided to check if an object is a file or a folder before creating txt file. After some difficulties (for example I've soon discovered that I can't redirect format-table that I was using to hide table headers to export-csv
http://blogs.msdn.com/b/powershell/archive/2007/03/07/why-can-t-i-pipe-format-table-to-export-csv-and-get-something-useful.aspx )
I came up with this solution:
gci e:\mytree -r |
select fullname,#{n='folder';e={ switch ($_.psiscontainer) {
true {1}
false {0}
}
}
} | % {($_.fullname,$_.folder) -join ","} | out-file e:\structure.txt
that gets me this output:
E:\mytree\folder1,1
E:\mytree\folder2,1
E:\mytree\folder3,1
E:\mytree\file1.txt,0
E:\mytree\file12.txt,0
E:\mytree\folder1\folder.with.dots,1
E:\mytree\folder1\folder.with.dots\file inside folder with dots.txt,0
E:\mytree\folder3\file4.doc,0
So I can easily split two parameters and use new-item cmdlet accordingly to object type.
Both FileInfo type an DirectoryInfo type has got a property PSIsContainer, that allow you to see if the object is a directory or not.
PS C:\temp> (Get-Item 'Hello world.exe').PSIsContainer
False
PS C:\temp> (Get-Item 'c:\temp').PSIsContainer
True
I modified your code slightly to this:
gc c:\scripts\files.txt | % {
$item = Get-item $_
Write-Host $item.fullName $item.PSIscontainer
}
Now, my output looks like this:
C:\Scripts\mytree\folder1 True
C:\Scripts\mytree\folder2 True
C:\Scripts\mytree\file1.txt False
C:\Scripts\mytree\file2.txt False
C:\Scripts\mytree\folder.with.dots True
C:\Scripts\mytree\folder.with.dots\file inside folder with dots.txt False