Import CSV using PowerShell: date conversion - sql

I'm importing CSV files to the MS SQL, and in different CSV files I have different date types. My code:
## Input Variables
$csvPath = "F:\Test\table.csv"
$csvDelimiter = ";"
$serverName = "name"
$databaseName = "dbname"
$tableSchema = "dbo"
$tableName = "dates"
$encoding = "windows-1251"
Import-Csv -Path $csvPath -encoding $encoding -Delimiter $csvDelimiter | Write-SqlTableData -ServerInstance $serverName -DatabaseName $databaseName -SchemaName $tableSchema -TableName $tableName -Force
And, for example, dates in my CSVs are like:
12JAN2021:18:03:41.000000
The problem here is that month written by the letters
or
17.04.2021
The problem here is that SQL read this like "mm.dd.yyyy" but it's "dd.mm.yyyy"
How can I improve my code so it can automatically read date correctly and write it to my date or datetime field in the destination table?
Thanks so much!
update CSV example:
product;product_id;product_nm;dttm
220;text;some text;12JAN2021:18:03:41.000000
220;text;some text;1JAN2021:18:03:41.000000
564;text;some text;16JAN2021:18:03:41.000000

I don't believe there is an "automated way" of parsing your CSVs if they all have a different DateTime format. I believe you would need to inspect each file to see if the format is valid (cast [datetime] directly to the string) or if they have a format that needs to be parsed.
In example, both provided dates on your question, need to be parsed and can be parsed with [datetime]::ParseExact(..):
[datetime]::ParseExact(
'12JAN2021:18:03:41.000000',
'ddMMMyyyy:HH:mm:ss.ffffff',
[cultureinfo]::InvariantCulture
).ToString('MM.dd.yyyy')
[datetime]::ParseExact(
'17.04.2021',
'dd.MM.yyyy',
[cultureinfo]::InvariantCulture
).ToString('MM.dd.yyyy')
If you need help "updating" the DateTime column from your CSV you would need to provide more details on that.
In example, the CSV provided in the question can be updated as follows:
# Here you would use this instead:
# $csv = Import-Csv path/to/csv.csv -Delimiter ';'
$csv = #'
product;product_id;product_nm;dttm
220;text;some text;12JAN2021:18:03:41.000000
220;text;some text;1JAN2021:18:03:41.000000
564;text;some text;16JAN2021:18:03:41.000000
'# | ConvertFrom-Csv -Delimiter ';'
foreach($line in $csv)
{
$line.dttm = [datetime]::ParseExact(
$line.dttm,
'dMMMyyyy:HH:mm:ss.ffffff',
[cultureinfo]::InvariantCulture
).ToString('MM.dd.yyyy')
}
Now if we inspect the CSV it would look like this:
PS /> $csv | Format-Table
product product_id product_nm dttm
------- ---------- ---------- ----
220 text some text 01.12.2021
220 text some text 01.01.2021
564 text some text 01.16.2021
And all the process in pipeline would look as follows:
Import-Csv -Path $csvPath -Encoding $encoding -Delimiter $csvDelimiter |
ForEach-Object {
$_.dttm = [datetime]::ParseExact(
$_.dttm,
'dMMMyyyy:HH:mm:ss.ffffff',
[cultureinfo]::InvariantCulture
).ToString('MM.dd.yyyy')
$_
} | Write-SqlTableData.....

Related

powershell not exporting

hi i am running the following query in powershell:
Import-Module Hall.psm1
$Database = 'Report'
$Server = '192.168.1.2'
$Query = 'SELECT all * FROM [Report].[dbo].[TestView]'
$LogLocation = "\\Report\LogFile.csv"
$DynamicYear = (Get-Date).Year
$DynamicMonth = (Get-Culture).DateTimeFormat.GetMonthName((Get-Date).Month)
$FileDestination = "\\Report\MONTHLY REPORTS\"+$DynamicYear+"\"+$DynamicMonth+"\"
$Outputfilename='TestView-'+(Get-Date).ToString('MM-dd-yyyy')+'.csv'
$LocalCreate = 'C:\Scripts\LocalCreate\'
$FolderPathExtension = "Microsoft.PowerShell.Core\FileSystem::"
$CodeDestination = $FolderPathExtension+$FileDestination
$filedest=$LocalCreate+$outputfilename
$Logfile = $FolderPathExtension+$LogLocation
Invoke-sqlcmd -querytimeout 120 -query "
$Query
" -database $database -serverinstance $server |
ConvertTo-Csv -NoTypeInformation | # Convert to CSV string data without the type metadata
Select-Object -Skip 0 | # Trim header row, leaving only data columns
% {$_ -replace '"',''} | # Remove all quote marks
Set-Content -Path $filedest
(gc $filedest) | ? {$_.trim() -ne "" } | set-content $filedest
if(Test-Path ($filedest)) {
Move-Item -Path $filedest -Destination $CodeDestination -Force
$LogType = 'INFO'
$LogEntry = "$filedest MovedTo $To"
Write-Log -Message $LogEntry -Level $LogType -Logfile $Logfile
}
which works fine without any issue if the query has data.
however, if the query does not have any data it does not create a .csv. how can i get it to create a blank .csv? or .csv with headers only?
Use New-Item -ItemType File -Path $filedest before your Invoke-SqlCmd Or ConvertTo-Csv

Convert date format to CSV export

I'm new to Poweshell, and I made a script that the result of an SQL Select is exported to csv. However I have two columns that are in the date format mm-dd-yyyy and I would like it to be in the format yyyy-MM-dd HH: mm: ss.fff. I was able to transform just one column, in the other I couldn't find a way to convert.
Could someone help me to convert the two columns?
#Connect to SQL and run QUERY
$SQLServer = "xxxx"
$SQLDBName = "xxxx"
$SQLUsername = "xxxx"
$SQLPassword = "nxxx"
$OuputFile = "c:\SQL_Export.csv"
$SqlQuery = "SELECT
rtrim(HANDLE) as Handle,
rtrim(EMPRESA) as Empresa,
rtrim(FILIAL) as Filial,
rtrim(OPERACAO) as Operacao,
rtrim(PESSOA) as Pessoa,
rtrim(dataemissao) as Dataemissao,
rtrim(documentodigitado) as Documentodigitado,
rtrim(dataultimaliq) as Dataultimaliq,
rtrim(ehprevisao) as Ehprevisao,
rtrim(status) as Status,
rtrim(entradasaida) as Entradasaida
FROM [$SQLDBName].[dbo].[FN_DOCUMENTOS]
where DATAULTIMALIQ >= '20200101'
ORDER BY handle ASC"
##Delete the output file if it already exists
If (Test-Path $OuputFile ){
Remove-Item $OuputFile
}
Write-Host "INFO: Exporting data from $SQLDBName to $OuputFile" -foregroundcolor white -backgroundcolor blue
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; User ID = $SQLUsername; Password = $SQLPassword"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
#Output RESULTS to CSV
$DataSet.Tables[0] | select-Object -ExcludeProperty Dataemissao #{Name="Dataemissao";Expression={([datetime]$_.Dataemissao).ToString("yyyy-MM-dd HH:mm:ss.fff")}}, * | Export-Csv $OuputFile
(Get-Content $OuputFile) | Foreach-Object {$_ -replace '"', ""} | Set-Content $OuputFile -Encoding `UTF8`
the columns I need to convert are, "Dataemissao" and "Dataultimaliq"
In this case, you would need a very long line in the Select-Object:
$DataSet.Tables[0] |
Select-Object -ExcludeProperty Dataemissao, Dataultimaliq #{Name = "Dataemissao"; Expression= { '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]$_.Dataemissao }},
#{Name = "Dataultimaliq"; Expression= { '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]::ParseExact($_.Dataultimaliq, 'MMM dd yyyy hh:mm tt', [cultureinfo]"en-US") }},
* | Export-Csv -Path $OuputFile -Encoding UTF8 -NoTypeInformation
OR, you could add a small helper function above the code like
function Format-Date () {
param (
[string]$dateString,
[string]$format = 'MMM dd yyyy hh:mm tt'
)
$date = Get-Date
if ([datetime]::TryParseExact($dateString, $format, [cultureinfo]"en-US", 0, [ref]$date)) {
return '{0:yyyy-MM-dd HH:mm:ss.fff}'-f $date
}
}
and use that for the Dataultimaliq:
#{Name = "Dataultimaliq"; Expression= { Format-Date $_.Dataultimaliq }},
As you can see, I've put a second parameter $format in that defaults to ''MMM dd yyyy hh:mm tt', because that is the format the returned Dataultimaliq field is in.
You can also use that on the Dataemissao field, but I don't know what format that is in.. (apparently, your system can cast it to a DateTime object without any problems)
I see you read the csv file in later and remove the quotes from it. This can lead to various problems, because sometimes quotes are needed in a CSV file and you should not simply remove all of them. Have a look at this answer for code how you can do that safely
P.S. '{0:yyyy-MM-dd HH:mm:ss.fff}'-f [datetime]$_.Dataemissao is a more 'PowerShelly' way of getting a date in a specific format. It returns the same as ([datetime]$_.Dataemissao).ToString("yyyy-MM-dd HH:mm:ss.fff"), but is more concise.

Conflict with Get-Item

I have a line of code that has some conflict in it and I just cannot see what it is.
Here is the program
$Date = Get-Date -Format MM-dd-yyyy-HH-mm-ss-tt
<#*****SQL Session Variables*****#>
$Username = "sa"
$Password = "xyz" | ConvertTo-SecureString -asPlainText -Force
$Credentials = New-Object System.Management.Automation.PSCredential($Username,$Password)
$DatabaseNames = Invoke-SQLcmd -Query "Select * From sys.databases WHERE Name NOT LIKE '%Master%' AND Name NOT LIKE '%Tempdb%' AND Name NOT LIKE '%Model%' AND Name NOT LIKE '%Msdb%';" -ServerInstance "localhost" | Select Name -ExpandProperty Name
Write-Host "****Database Names****" -ForeGroundColor Red -BackGroundColor Black
$DatabaseNames
"`n`n"
<#****Get Database Size****#>
Push-Location
Import-Module sqlps -disablenamechecking
Invoke-Sqlcmd -Query "select name, physical_name, size * 8.0 / 1024 size from sys.master_files WHERE Type_Desc NOT LIKE '%LOG%' AND Name NOT LIKE '%Master%' AND Name NOT LIKE '%Tempdb%' AND Name NOT LIKE '%Model%' AND Name NOT LIKE '%Msdb%';" -ServerInstance "localhost" | Select #{Label="Database Name";Expression={$_.name}}, #{Label="Location";Expression={$_.physical_name}}, #{Label="SIze (MB)";Expression={$_.size}}
Pop-Location
"`n"
Start-Sleep 4
<#****Backup Databases****#>
ForEach ($DatabaseName in $DatabaseNames)
{
$SQLBackupPath1 = "C:\T2\SQlBackup\"
$SQLBackupPath2 = "$DatabaseName"
$SQLBackupPath3 = "_"
$SQLBackupPath4 = $Date
$SQLBackupPath5 = "_.bak"
$SQLBackupPath = $SQLBackupPath1 + $SQLBackupPath2 + $SQLBackupPath3 + $SQLBackupPath4 + $SQLBackupPath5
$SQLBackupPath
Backup-SQLDatabase -ServerInstance localhost -Database $DatabaseName -BackupFile $SQLBackupPath -Credential $Credentials
Write-Host "Database Backup of " -NoNewLine
Write-Host "$DatabaseName" -ForeGroundColor Red -BackGroundColor Black -NoNewLine
Write-Host " has been completed"
"`n"
}
<#****Delete Databases****#>
ForEach ($DatabaseName in $DatabaseNames)
{
Push-Location
Import-Module sqlps -disablenamechecking
Invoke-Sqlcmd -Query "EXEC msdb.dbo.sp_delete_database_backuphistory #database_name = N'$DatabaseName';" -ServerInstance "localhost"
Invoke-Sqlcmd -Query "USE [master];" -ServerInstance "localhost"
Invoke-Sqlcmd -Query "ALTER DATABASE [$DatabaseName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;" -ServerInstance "localhost"
Invoke-Sqlcmd -Query "DROP DATABASE [$DatabaseName];" -ServerInstance "localhost"
Pop-Location
Write-Host "Database: " -NoNewLine
Write-Host "$DatabaseName" -ForeGroundColor Red -BackGroundColor Black -NoNewLine
Write-Host " has been Dropped"
"`n"
}
Get-ChildItem -Path C:\T2\SQLBackup\ | Where-Object {$_.Name -match "ApOps*" } | Sort-Object LastWriteTime -Descending
If I move the final GetChildItem line under the first line $Date I get output. However if I use this in a loop or where it currently sits no results are returned. This makes me believe that there is some conflicting statement in the code. If so I cannot seem to find it and I am losing my mind looking for it. Please help.
You're being bitten by the output formatting, and being a bit careless with your output. Consider this, I'm in a directory with a couple of files:
List the files, simple and works fine:
PS D:\test> Get-ChildItem # list the files
Directory: D:\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 02/11/2016 07:01 1640 log.txt
-a---- 02/11/2016 07:01 0 testfile
Output an empty string, then list the files, simple and works fine. One blank line extra at the start:
PS D:\test> ""; Get-ChildItem
Directory: D:\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 02/11/2016 07:01 1640 log.txt
-a---- 02/11/2016 07:01 0 testfile
Get the string length, then list the files. Wait what? The output formatter is taking property you asked for from the first output it receives (string Length) and is then applying that to everything it receives (files) until the pipeline is empty:
PS D:\test> "" | select Length; Get-ChildItem # select string length, then...
Length # missing columns ??!!
------
0 # string length
1640 # file length
0 # file length
Get some property which doesn't exist, then list the files. This is the same idea, only this time it's not showing anything for the string or anything for the files, because Foobar doesn't exist on either type:
PS D:\test> "" | select Foobar; Get-ChildItem # select property which doesn't exist
Foobar
------
# ???? this is your "no output"
You are doing this kind of thing - sending mixed types to the output pipeline. They still go to the output, if you captured them in variables or wrote them to files the data would be there, but the output formatter which tries to work out how to display them on the console takes the first thing it gets and formats the output against that, assuming you'll have a consistent output. And the first complex objects it gets come from your code here:
Invoke-Sqlcmd -Query "select name, physical_name, size * 8.0 / 1024 size from sys.master_files WHERE Type_Desc NOT LIKE '%LOG%' AND Name NOT LIKE '%Master%' AND Name NOT LIKE '%Tempdb%' AND Name NOT LIKE '%Model%' AND Name NOT LIKE '%Msdb%';" -ServerInstance "localhost" | Select #{Label="Database Name";Expression={$_.name}}, #{Label="Location";Expression={$_.physical_name}}, #{Label="SIze (MB)";Expression={$_.size}}
# where you select these properties for display:
# Database Name, Location, Size (MB)
# None of these properties exist on files or directories,
# so they don't get shown on screen.
Fix:
$x = Invoke-SqlCmd ___
$y = Get-ChildItem
$x | Format-Table
$y | Format-Table
Or use more Write-Host, or output to files.

Address Variables with PowerShell and Import-CSV foreach-loop

Input file:
"Server1","lanmanserver"
"Server2","lanmanserverTest"
Program
$csvFilename = "D:\Scripts\ServerMonitorConfig.csv"
$csv = Import-Csv $csvFilename -Header #("ServerName","ServiceName")
foreach ($line in $csv) {
Write-Host "ServerName=$line.ServerName ServiceName=$line.ServiceName"
}
What I want:
Server-Name=Server1 ServiceName=lanmanserver
Server-Name=Server2 ServiceName=lanmanserverT
What I'm getting:
ServerName=#{ServerName=Server1; ServiceName=lanmanserver}.ServerName
ServiceName=#{ServerName=Server1; ServiceName=lanmanserver}.ServiceN
ame ServerName=#{ServerName=Server2;
ServiceName=lanmanserverTest}.ServerName
ServiceName=#{ServerName=Server2; ServiceName=lanmanserverTest}.
ServiceName
I really don't care if the Headers come from the first row of the CSV or not, I'm flexible there.
You usually see subexpressions or format strings used to solve that:
Subexpression:
Write-Host "ServerName=$($line.ServerName) ServiceName=$($line.ServiceName)"
Format string:
Write-Host ('ServerName={0} ServiceName={1}' -f $line.ServerName,$line.ServiceName)

Read var directly from csv

Is it possible to read a variable directly by loading a csv?
My csv looks like this:
Var,Path
$SrcHost,\\computer
Is there a possibility to import-csv and put the path into the var?
import-csv test3.csv | foreach-object {
iex "$($_.var) = ""$($_.path)"""
}
You can also use new-variable (nv):
import-csv csvfile.csv | % { nv -name ($_.var) -value ($_.path) }
However to make this work you have to:
remove the $ from the source csv
or, trim $ as described by the comments below
or, select your variable as ${$srchost}
How bout this:
Get-Content -Path test.csv | Select-Object -Skip 1 | ForEach-Object { $_.Replace(",", "=`"") +
"`"" } | Invoke-Expression