I am attempting to turn the file below into one that contains no comments '#', no blank lines, no unneeded spaces, and only one entry per line. I'm unsure how to run the following code without the need to output the file and then reimport it. There should be code that doesn't require that step but I can't find it. The way I wrote my script also doesn't look right to me even though it works. As if there was a more elegant way of doing what I'm attempting but I just don't see it.
Before File Change: TNSNames.ora
#Created 9_27_16
#Updated 8_30_19
AAAA.world=(DESCRIPTION =(ADDRESS_LIST =
(ADDRESS =
(COMMUNITY = tcp.world)
(PROTOCOL = TCP)
(Host = www.url1111.com)
(Port = 1111)
)
)
(CONNECT_DATA = (SID = SID1111)
)
)
#Created 9_27_16
BBBB.world=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=url2222.COM)(Port=2222))(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=url22222.COM)(Port=22222)))(CONNECT_DATA=(SID=SID2222)))
CCCC.world=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=url3333.COM)(Port=3333))(CONNECT_DATA=(SID=SID3333)))
DDDD.url =(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=URL4444 )(Port=4444))(ADDRESS=(COMMUNITY=TCP.world)(PROTOCOL=TCP)(Host=URL44444 )(Port=44444)))(CONNECT_DATA=(SID=SID4444 )(GLOBAL_NAME=ASDF.URL)))
#Created 9_27_16
#Updated 8_30_19
After File Change:
AAAA.world=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=www.url1111.com)(Port=1111)))(CONNECT_DATA=(SID=SID1111)))
BBBB.world=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=url2222.COM)(Port=2222))(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=url22222.COM)(Port=22222)))(CONNECT_DATA=(SID=SID2222)))
CCCC.world=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=url3333.COM)(Port=3333))(CONNECT_DATA=(SID=SID3333)))
DDDD.url=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(COMMUNITY=tcp.world)(PROTOCOL=TCP)(Host=URL4444)(Port=4444))(ADDRESS=(COMMUNITY=TCP.world)(PROTOCOL=TCP)(Host=URL44444)(Port=44444)))(CONNECT_DATA=(SID=SID4444)(GLOBAL_NAME=ASDF.URL)))
Code:
# Get the file
[System.IO.FileInfo] $File = 'C:\temp\TNSNames.ora'
[string] $data = (Get-Content $File.FullName | Where-Object { !$_.StartsWith('#') }).ToUpper()
# Convert the data. This part is where any (CONNECT_DATA entry ends up on it's own line.
$Results = $data.Replace(" ", "").Replace("`t", "").Replace(")))", ")))`n")
# Convert $Results from BaseType of System.Object to System.Array
$Path = '.\.vscode\StringResults.txt'
$Results | Out-File -FilePath $Path
$Results = Get-Content $Path
# Find all lines that start with '(CONNECT_DATA'
for ($i = 0; $i -lt $Results.Length - 1; $i++) {
if ($Results[$i + 1].StartsWith("(CONNECT_DATA")) {
# Add the '(CONNECT_DATA' line to the previous line
$Results[$i] = $Results[$i] + $Results[$i + 1]
# Blank out the '(CONNECT_DATA' line
$Results[$i + 1] = ''
}
}
# Remove all blank lines
$FinalForm = $null
foreach ($Line in $Results) {
if ($Line -ne "") {
$FinalForm += "$Line`n"
}
}
$FinalForm
So the crux of your problem is that you have declared $data as a [string] which is fine because probably some of your replace operations work better as a single string. Its just that $Results also then ends up being a string so when you try to index into $Results near the bottom these operations fail. You can however easily turn your $Results variable into a string array using the -split operator this would eliminate the need to save the string to disk and import back in just to accomplish the same. See comments below.
# Get the file
[System.IO.FileInfo] $File = 'C:\temp\TNSNames.ora'
[string] $data = (Get-Content $File.FullName | Where-Object { !$_.StartsWith('#') }).ToUpper()
# Convert the data. This part is where any (CONNECT_DATA entry ends up on it's own line.
$Results = $data.Replace(' ', '').Replace("`t", '').Replace(')))', ")))`n")
# You do not need to do this next section. Essentially this is just saving your multiline string
# to a file and then using Get-Content to read it back in as a string array
# Convert $Results from BaseType of System.Object to System.Array
# $Path = 'c:\temp\StringResults.txt'
# $Results | Out-File -FilePath $Path
# $Results = Get-Content $Path
# Instead split your $Results string into multiple lines using -split
# this will do the same thing as above without writing to file
$Results = $Results -split "\r?\n"
# Find all lines that start with '(CONNECT_DATA'
for ($i = 0; $i -lt $Results.Length - 1; $i++) {
if ($Results[$i + 1].StartsWith('(CONNECT_DATA')) {
# Add the '(CONNECT_DATA' line to the previous line
$Results[$i] = $Results[$i] + $Results[$i + 1]
# Blank out the '(CONNECT_DATA' line
$Results[$i + 1] = ''
}
}
# Remove all blank lines
$FinalForm = $null
foreach ($Line in $Results) {
if ($Line -ne '') {
$FinalForm += "$Line`n"
}
}
$FinalForm
Also, for fun, try this out
((Get-Content 'C:\temp\tnsnames.ora' |
Where-Object {!$_.StartsWith('#') -and ![string]::IsNullOrWhiteSpace($_)}) -join '' -replace '\s' -replace '\)\s?\)\s?\)', ")))`n" -replace '\r?\n\(Connect_data','(connect_data').ToUpper()
I have file 1.csv
number, name # column name
1,john
2,mike
3,test
4,test2
...
I created function for returning all values from this csv (number,name)
Function Get-CSV {
[CmdletBinding()]
param (
# CSV File path
[Parameter(Mandatory=$true)][string]$path
)
#Create an hashtable variable
[hashtable]$return = #{}
Import-Csv $path |
ForEach-Object {
$number = $_.number
$name = $_.name
$return.name = $name
$return.number = $number
return $return
}
# return $return un-commenting this line don't change output
}
# calling function
$a = Get-CSV "C:\Users\1.csv"
$a.number
$a.name
I get only one (last row from CSV) - a.name = test2 and a.number = 4
How to get all rows from CSV when calling this function ?
You need to construct an array of hashtables for this to work. Even better, I would create an array of objects because it gives you control over the property names. You can change the function definition to:
Function Get-CSV {
[CmdletBinding()]
param (
# CSV File path
[Parameter(Mandatory=$true)][string]$path
)
#Create an empty array variable
$return = #()
Import-Csv $path |
ForEach-Object {
$return += ,(New-Object PSObject -property #{'name'= $_.name; 'number'= $_.number})
}
return $return
}
This gives you:
$a = Get-CSV "C:\Users\1.csv"
$a
name number
---- ------
john 1
mike 2
test 3
test2 4
Note
I'm not sure on your exact use case, but Import-Csv already gives you the information back as an object, so there may not be a need to create a separate one.
After lot of googling and try/errors found solution:
Function Get-CSV {
[CmdletBinding()]
param (
# CSV File path
[Parameter(Mandatory=$true)][string]$path
)
Import-Csv $path |
ForEach-Object {
$number = $_.number
$name = $_.name
[pscustomobject]#{
name = $name
number = $number
}
}
}
param([string]$roles,[string]$members)
Suppose I am passing input on the command line like this:
PS> role1,role2,role3,role4 member1,member2,,,,member3,,member4
The array I expect for this would be:
$array = #(
#('role1', 'member1,member2'),
#('role2', ''),
#('role3', 'member3'),
#('role4', 'member4')
)
I know to turn string to array:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
Now how do I combine $roles with $members so that each role will be associated with member(s)? and how wouldIi generate the array dynamically?
Pseudocode:
$array = #()
($roles+$members) | %{
$role = $_.roles
if ($_.members) {
$_.members -split ',,' | ForEach-Object { $array += $role $_ }
} else {
$array += $role
}
}
Note: I am splitting members as an index of its own for each double comma because apparently semicolons aren't accepted on a command line because they break the command line, so I have to use double comma as delimiter.
Note 2: notice the 4 commas: ,,,, this indicates that role2 does not have members to add, so in essence it means between the 4 commas is no input for member to that index/item (role2), i.e. ,,EMPTY,,.
If you really want to stick with this parameter format, you can create the desired output array as follows:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$i = 0
$array = #(foreach ($role in $roles) {
, ($role, $members[$i++])
})
Note that if you pass your arguments from PowerShell, you need to quote them, as PowerShell will otherwise parse them as an array.
And with quoting you're free to use ; in lieu of ,,, for instance, to separate the member groups.
A better way to represent the argument data for later processing is to create an array of custom objects rather than a nested array:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$i = 0
$array = #(foreach ($role in $roles) {
[pscustomobject] #{
Role = $role
Members = $members[$i++] -split ','
}
})
Each object in $array now has a .Role and a .Members property, the latter containing the individual members as a an array of strings.
Alternatively, you could create a[n ordered] hashtable from the input, keyed by role name, but that is only necessary if you need to access roles by name or if you wanted to rule out duplicate roles having been specified.
Here's an alternative argument format that is easier to understand:
$rolesAndMembers = 'role1 = member1,member2 ; role2= ; role3=member3 ; role4=member4'
$array = #(foreach ($roleAndMembers in ($rolesAndMembers -replace ' ' -split ';')) {
$role, $members = $roleAndMembers -split '='
[pscustomobject] #{
Role = $role
Members = $members -split ','
}
})
Your parameter format is rather bizarre, but here's one way:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ',,'
$result = #()
for ( $i = 0; $i -lt $roles.Count; $i++ ) {
$result += ,#($roles[$i],$members[$i])
}
I would recommend redesigning the script to use standard PowerShell parameters (the engineering effort would be worth it, IMO).
I'd strongly recommend using hashtables/dictionaries to pass these role mappings:
param(
[System.Collections.IDictionary]$RoleMembers
)
# now we can access each mapping by role name:
$RoleMembers['role1'] # member1, member2
# or iterate over them like an array:
foreach($role in $RoleMembers.Keys){
$RoleMembers[$role]
}
You could use one of the construct the input argument from your current input strings:
$roles = 'role1,role2,role3,role4' -split ','
$members = 'member1,member2,,,,member3,,member4' -split ','
$roleMembers = #{}
for ($i = 0; $i -lt $roles.Count; $i++) {
# `Where Length -ne 0` to filter out empty strings
$roleMembers[$roles[$i]] = $members[($i*2)..($i*2+1)] |Where Length -ne 0
}
I have a problem with LastWriteTime.
I need to split this value:
09/26/2014 10:14:34 09/26/2014 08:09:59
I tried this:
foreach ($line in $data){write-host $line}.
But I need to write it to $date1 and $date2.
This is the code I use:
$Folder = Get-ChildItem "\\SERVER-14\data$\backup" -Exclude $ExcludeFolders -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select Name, LastWriteTime -first 2
$LastFolderName = $Folder.Name
$LastFolderTime = $Folder.LastWriteTime
If you know you're always getting 2 objects as specified by -first 2 in your Select-Object, you can directly reference them:
$date1 = $folder[0].lastWriteTime
$date2 = $folder[1].lastWriteTime
If all you want to do is assign the first and second last write time to variables then append this to your script:
$date1 = $Folder[0].LastWriteTime
$date2 = $Folder[1].LastWriteTime
But you won't always know if the folder contains two items. By right you should be doing all the work for each item inside the foreach loop
foreach ($file in $Folder)
{
#Do something
}
For splitting strings, you can use the Powershell -Split operator which uses a regex argument to control the split, or for simple cases like this you can use the dotnet [string] split method, which splits on an array of characters:
PS C:\> $line -split ' '
09/26/2014
10:14:34
09/26/2014
08:09:59
PS C:\> $line.split(' ')
09/26/2014
10:14:34
09/26/2014
08:09:59
Either way, you're going to get an array of the split elements. Then you can selectively assign those to variables using array slicing:
PS C:\> $date1,$date2 = ($line -split ' ')[0,2]
PS C:\> $date1
09/26/2014
PS C:\> $date2
09/26/2014
PS C:\> $date1,$date2 = $line.split(' ')[0,2]
PS C:\> $date1
09/26/2014
PS C:\> $date2
09/26/2014
See:
about_split
string methods
$value = "09/26/2014 10:14:34 09/26/2014 08:09:59"
$data = $value.Split(' ')
# $data contains 4 values --> date, time, date, time
#i set up some variables
$date = $true;
$savedDate = "";
# Here we process the $data
$data |
# For each in powershell = %
%{
#every odd line ( if $date is true), we save the date value
if($date){$date=$false;$savedDate=$_}
# every even line (if $date is false), we stick together the date and time
else{$date=$true;$savedDate+ " " + $_;}
}
Very "straight forward" solution...(maybe not so simple, sorry)
Split on whitespace
Iterate over all the strings,
each time you have a date and a time string following each other.
Thus save the first one as the date,
every second value contains the time, put it together and continue with the next date...
I have this:
$str = "name = username`nemail = user#example.com"
Which is LF separated string, as part of an INI file. I want to create associative array so that for instance $vars['name'] will return 'username'. This is how I split it to lines:
$vars = ([regex]'\n').Split($str) | %{ $_.Trim() }
What change should I make to the line above?
If you are a lucky owner of Powershell 4 then you can use ConvertFrom-StringData cmdlet:
$str = "name = username`nemail = user#example.com"
$vars = ConvertFrom-StringData $str
Result:
PS C:\> $vars
Name Value
---- -----
name username
email user#example.com
A more PoSh version of PeterK's answer would look like this:
$vars = #{}
$str.Trim() -split "\s*`n\s*" | % {
$key, $value = $_ -split '\s*=\s*'
$vars[$key] = $value
}
Using $vars[$key] = $value instead of $vars.Add($key, $value) avoids errors in case you have duplicate keys.
You could also split the string into an array of alternating key and value fields and then fill the hashtable from that array:
$vars = #{}
$list = $str.Trim() -split "\s*`n\s*" -split '\s*=\s*'
while ($list) {
$key, $value, $list = $list
$vars[$key] = $value
}
Each of the examples can be mangled into a single line by separating the statements with semicolons:
$vars = #{}; $str.Trim() -split "\s*`n\s*" | % { $key, $value = $_ -split '\s*=\s*'; $vars[$key] = $value }
$vars = #{}; $list = $str.Trim() -split "\s*`n\s*" -split '\s*=\s*'; while ($list) { $key, $value, $list = $list; $vars[$key] = $value }
It's not a one-liner, but one way to do it is:
$hashtable = #{};
$key_value_pairs = $str.Split("`n");
foreach($key_value in $key_value_pairs)
{
$key_value_pair = $key_value.Split('=');
$hashtable.Add($key_value_pair[0].Trim(), $key_value_pair[1].Trim());
}
The input string is first split into an array, each item containing a key-value pair in a single line. For each key-value pair, we split them into a 'key' and 'value' on "=", again into a regular array. Both the key and value are trimmed and added to the hash table. Once this is done, $hashtable["name"] will return "username" and $hashtable["email"] will return "user#example.com"