I have the following foreach loop in a powershell script:
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object) {
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
The entire script is wrapped with a catch/try block and works OK for errors.
I now have a requirement that if one of the files(scripts) produces an error, the loop will ignore that and move on to the next file.
Could I do this with nested catch try blocks or is there a way to resume the loop on an error?
Adding an additional Try..Catch would probably be the way to go. For example you could modify your ForEach-Object blocks as follows:
Try {
$SqlCmd.CommandText = $_.Trim();
$reader = $SqlCmd.ExecuteNonQuery()
} Catch {
Write-Error "$($SqlCmd.CommandText) resulted in an error"
$_
}
Note the $_ within the Catch block would contain the error that occurred. After the Catch is executed the rest of the script should then carry on as normal.
Use the ErrorAction preference as SilentlyContinue wherever there is an executing statement or a statement which you feel is likely to throw an error. In you current script, you can do this -
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object)
{
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments)
{
$true
{
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' -ErrorAction SilentlyContinue |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false
{
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\n\s*go\s*\r\n?' -notmatch '^\s*$' -ErrorAction SilentlyContinue |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
See this and this link for details.
Related
I want throw an error if I call the function with a TypeAdressage not equal to "Dynamique" or "Statique" but it throw the error when i call it with "Dynamique"
function Changer-Type {
param(
[string]$Identifiant,
[string]$TypeAdressage,
[string]$Path
)
if ($TypeAdressage -ne "Dynamique"-or ($TypeAdressage -ne "Statique")){
Write-Error "Type Adressage impossible"
}
else{
if(-not(Test-Path $Path -PathType Leaf) -or [IO.Path]::GetExtension($Path) -ne '.csv') {
throw 'File does not exist or is not a Csv...'
}
$computers = Import-Csv -Path $Path -Delimiter ';'
$computers | Where-Object { $_.Identifiant -eq $Identifiant } |
ForEach-Object { $_.TypeAdressage = $TypeAdressage }
# write the updated $computers object array back to disk
$computers | Export-Csv -Path $Path -Delimiter ';' -NoTypeInformation
}
}
$csvPath = 'C:\Temp\Peripherique.csv'
Changer-Type -Identifiant "Q00032" -TypeAdressage "Dynamique" -Path $csvPath
Changer-Type -Identifiant "PWIN10" -TypeAdressage "test" -Path $csvPath
Why not let PowerShell validate the parameter $TypeAdressage to always be one of the two (or more) predefined strings?
function Changer-Type {
param(
[string]$Identifiant,
[ValidateSet('Dynamique','Statique')]
[string]$TypeAdressage,
[string]$Path
)
if(-not(Test-Path $Path -PathType Leaf) -or [IO.Path]::GetExtension($Path) -ne '.csv') {
throw 'File does not exist or is not a Csv...'
}
$computers = Import-Csv -Path $Path -Delimiter ';'
$computers | Where-Object { $_.Identifiant -eq $Identifiant } |
ForEach-Object { $_.TypeAdressage = $TypeAdressage }
write the updated $computers object array back to disk
$computers | Export-Csv -Path $Path -Delimiter ';' -NoTypeInformation
}
Changer-Type -TypeAdressage Dynamique
As extra bonus, using [ValidateSet()] will also give you intellisense when coding:
I suggest changing your if statement. Because you are saying:
if
$TypeAdressage not equal to "Dynamique"
or
$TypeAdressage not equal to "Dynamique".
That if statement will always be true, because $TypeAdressage will never be both "Dynamique" and "Dynamique".
How I can replace string which contains quotes and brackets in a file using powershell ?
I have file with settings c:\app\settings.js
app_set("safe.mode", true);
app_set("api.uri", "https://apiurl.com");
app_set("api.version", 1);
And script ChangeSettins.ps1 but it actually doesn't change strings in a file.
$SettingsFile = c:\app\settings.js
$SAFEMODE = 'app_set("safe.mode", true);'
$APIURI = 'app_set("api.uri", "https://apiurl.com");'
$APIVERSION = 'app_set("api.version", 1);'
$SAFEMODE_PROFILE = Get-Content $SettingsFile | Select-String -Pattern "safe.mode"
#$SAFEMODE_PROFILE
$APIURI_PROFILE = Get-Content $SettingsFile | Select-String -Pattern "api.uri"
#$APIURI_PROFILE
$APIVERSION_PROFILE = Get-Content $SettingsFile | Select-String -Pattern "api.version"
#$APIVERSION_PROFILE
If ("$SAFEMODE_PROFILE" -eq "$SAFEMODE") {
Write-Host "safe mode is enabled"
}
Else {
Write-Host "enabling safe mode"
(Get-Content $SettingsFile) | Foreach-Object { $_ -Replace "$SAFEMODE_PROFILE", "$SAFEMODE" } | Set-Content $SettingsFile
}
If ("$APIURI_PROFILE" -eq "$APIURI") {
Write-Host "uri is correct"
}
Else {
Write-Host "updating uri"
(Get-Content $SettingsFile) | Foreach-Object { $_ -Replace "$APIURI_PROFILE", "$APIURI" } | Set-Content $SettingsFile
}
If ("$APIVERSION_PROFILE" -eq "$APIVERSION") {
Write-Host "api version is 1"
}
Else {
Write-Host "changing api version to 1"
(Get-Content $SettingsFile) | Foreach-Object { $_ -Replace "$APIVERSION_PROFILE", "$APIVERSION" } | Set-Content $SettingsFile
}
Replacement Operator
The -replace operator replaces all or part of a value with the
specified value using regular expressions.
Apply the Regex.Escape(String) Method where necessary, e.g. as follows:
$_ -Replace [regex]::Escape("$APIURI_PROFILE"), "$APIURI"
I currently have a CSV file that has 2,440 lines of data. The data looks something like:
server1:NT:Y:N:N:00:N
server2:NT:Y:N:n:33:N
This is what I have so far:
$newCsvPath = Get-Content .\sever.csv |
Where-Object { $_ -notmatch '^#|^$|^"#' }
[int]$windows = 0
[int]$totalsever = 0
$Results = #()
$date = Get-Date -Format g
Clear-Content .\results.csv -Force
foreach ($thing in $newCsvPath) {
$totalsever++
$split = $thing -split ":"
if ($split[1] -contains "NT") {
$windows++
$thing | Out-File results.csv -Append -Force
} else {
continue
}
}
Clear-Content .\real.csv -Force
$servers = Get-Content results.csv
foreach ($server in $servers) {
$server.Split(':')[0] | Out-File real.csv -Append -Force
}
My issue is that when the script gets to the $server.Split(':')[0] | Out-File real.csv -Append -Force part, for some reason it only outputs 1,264 lines instead of all 2,440 to "real.csv". However, when I remove | Out-File real.csv -Append -Force, $server stores ALL 2,400 names of servers.
Does anyone have any idea of why this is happening?
I am trying to create a log reader. The data looks like so:
2017-11-27 13:24:41,791 [8] INFO CTSipEndpoint.CLogger.provider.gsiplib [(null)] - -00001 [Info] Info | 4744 | REGISTERdialog[1] 2-e:5;t:1-3 (dn:85188)
2017-11-27 13:24:41,791 [8] INFO CTSipEndpoint.CLogger.provider.gsiplib [(null)] - -00001 [Info] Info | 4744 | REGISTERdialog[1] event 2 REG/accepted
I am trying to do the following:
Return only lines in the last 48 hours to query further.
From above return any lines that contain the following phrases: "error"
"device","does not exist", "Could not identify speaker!","warn"
So far i have only been able to get this to work in an inefficient way, which runs against the file for each phrase and appends an array. Unfortunately this means that the date time becomes non-sequential. I need to now sort the content object at the end of the script to it be in sequence, or find a way to run this query smarter. Here is my script for reference:
$logfile = "C:\users\test\desktop\programlogs.log"
$content = ""
cat $logfile |
Select-String "ERROR" -SimpleMatch |
select -expand line |
foreach {
$_ -match '(.+)\s\[(ERROR)\]\s(.+)'| Out-Null
$error_time = [datetime]($matches[1]).split(",")[0]
if ($error_time -gt (Get-Date).AddDays(-2)) {
$content += $_ + "`n"
}
}
cat $logfile |
Select-String "device" -SimpleMatch |
select -expand line |
foreach {
$_ -match '(.+)\s\[(device)\]\s(.+)'| Out-Null
$error_time = [datetime]($matches[1]).split(",")[0]
if ($error_time -gt (Get-Date).AddDays(-2)) {
$content += $_ + "`n"
}
}
cat $logfile |
Select-String "does not exist" -SimpleMatch |
select -expand line |
foreach {
$_ -match '(.+)\s\[(does not exist)\]\s(.+)'| Out-Null
$error_time = [datetime]($matches[1]).split(",")[0]
if ($error_time -gt (Get-Date).AddDays(-2)) {
$content += $_ + "`n"
}
}
cat $logfile |
Select-String "Could not identify speaker!" -SimpleMatch |
select -expand line |
foreach {
$_ -match '(.+)\s\[(Could not identify speaker!)\]\s(.+)'| Out-Null
$error_time = [datetime]($matches[1]).split(",")[0]
if ($error_time -gt (Get-Date).AddDays(-2)) {
$content += $_ + "`n"
}
}
cat $logfile |
Select-String "Warn" -SimpleMatch |
select -expand line |
foreach {
$_ -match '(.+)\s\[(Warn)\]\s(.+)'| Out-Null
$error_time = [datetime]($matches[1]).split(",")[0]
if ($error_time -gt (Get-Date).AddDays(-2)) {
$content += $_ + "`n"
}
}
$content = $content | select -uniq
$file = "c:\temp\shortenedlog.txt"
$content| Add-Content -Path $file
Here's a short take, let me know if this helps or if needs tweaking:
$newlog=#()
$logfile = get-content C:\temp\programlogs.log
$searchFor="error","device","does not exist","Could not identify speaker!","warn"
foreach($line in $logfile){
if($line.Length -gt 18){
$datetime=date $line.substring(0,$line.indexof(","))
if(((date)-$datetime).TotalHours -lt 49){
$keep=$false
foreach($item in $searchFor){
if($line.contains($item)){ $keep=$true }
}
if ($keep){$newlog+=$line}
}
}
}
$newlog | sort | % {add-content C:\temp\NewLog.log $_}
Came up with a solution which I have fed in the days i am interested in, followed by the search criteria. Used this solution as works quickly.
#Set parameters.
$File = "c:\temp\RefinedLogs.txt"
$DateParam = (Get-Date).AddDays(-1).ToString('yyyy-MM-dd')
$DateParam1 = (Get-Date).ToString('yyyy-MM-dd')
$SearchForDate = #("$dateparam", "$dateparam1")
$SearchFor=#("error","device","does not exist","Could not identify speaker!","warn")
#Filter file with dates set in $SearchForDate.
$DateFiltered = Get-Content '.\MyAPP.log' | Select-String -Pattern $SearchForDate -SimpleMatch
#Filter variable for phrases set in $SearchFor.
$Content = $DateFiltered | Select-String -Pattern $SearchFor -SimpleMatch
#Make results readable
ForEach($line in $content){
$Object = "$line" + "`n"
$FinalResult += $Object
}
#Output results.
write-host $FinalResult
I'm using the following code to load SQL scripts from a folder and execute them.
foreach ($sqlScript in Get-ChildItem -path "$pathToScripts" -Filter *.sql | sort-object) {
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript.FullName -Encoding UTF8 | Out-String) -split '\r?\ngo\r?\n' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
I've been asked if its possible to have some sort of table of contents to execute these files in a particular sequence without having to rename them. Is it possible to have a comma delimited file that I could loop through and load each file in the same sequence?
Edit
This is the code I think I'm going to go with:
Get-Content $executionOrder
ForEach ($file in $executionOrder) {
$sqlScript = $pathToScripts + "\" + $file
Write-Host "Running Script " $sqlScript.Name
#Execute the query
switch ($removeComments) {
$true {
(Get-Content $sqlScript -Encoding UTF8 | Out-String) -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
$false {
(Get-Content $sqlScript -Encoding UTF8 | Out-String) -split '\r?\ngo\r?\n' |
ForEach-Object { $SqlCmd.CommandText = $_.Trim(); $reader = $SqlCmd.ExecuteNonQuery() }
}
}
}
Is it possible to have a comma delimited file that I could loop through and load each file in the same sequence
Yes. You just need to update your outer look logic to account for that input. With only minor changes you can get what you want.
foreach ($sqlScript in (Import-CSV $pathtoCSV)){
# Process file.
}
That would work if you wanted a CSV file input as you requested. In comments it looks like you are getting a static list of file names in a predefined directory.
$pathToFileList = "C:\Bagel.txt"
$rootScriptDirectory = "\\path\to\scripts"
$removeComments = $true
Get-Content $pathToFileList | ForEach-Object{
# Build the full file paths
$scriptFilePath = [io.path]::Combine($rootScriptDirectory,$_)
# If this file actually exists then it should be processed
If(Test-Path $scriptFilePath -PathType Leaf){
# Get the file contents
$fileContents = Get-Content $scriptFilePath -Encoding UTF8 | Out-String
# Clean the file contents as required
if($removeComments){
$queries = $fileContents -replace '(?s)/\*.*?\*/', " " -split '\r?\ngo\r?\n' -notmatch '^\s*$'
} else {
$queries = $fileContents -split '\r?\ngo\r?\n'
}
# Execute each query of the file
$queries | ForEach-Object{
$SqlCmd.CommandText = $_.Trim()
$reader = $SqlCmd.ExecuteNonQuery()
}
# Hilarity ensues
} else {
Write-Warning "Could not locate the file '$scriptFilePath'"
}
}
The features of switch are a little wasted here since you only have two states. Move the things that actually get changes into an if block. Get the file list and test that the file exists. Open it and parse the queries from it with your already set logic.