Power shell custom function in script - sql-server

I had prepared a script to pull some report w.r.t SQL server and out put will be pushed to different CSV sheets. After output is generated, all the CSV's are merged to single Excel file with the help of custom created function and that excel will be sent to my email address.
While running htrough powershell_ise.exe, it is running fine and I am receiving the email successfully. When I scheduled the same script, I am receiving the email but with out excel attachments. I am suspecting that custom created function is not used, because I dont see any converted excel files in the desired location.
I tried all possible ways, like dot sourching, pasting the function in the script itself but no luck.
I am a beginner in powershell, can some one please help me if i am missing some thing.
Thanks,
Anil
Function Merge-CSVFiles
{
Param(
$CSVPath = "D:\Anil\Missing_Indexes", ## Soruce CSV Folder
$XLOutput="D:\Anil\Missing_Indexes.xls" ## Output file name
)
$csvFiles = Get-ChildItem ("$CSVPath\*") -Include *.csv
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$Excel.sheetsInNewWorkbook = $csvFiles.Count
$workbooks = $excel.Workbooks.Add()
$CSVSheet = 1
Foreach ($CSV in $Csvfiles)
{
$worksheets = $workbooks.worksheets
$CSVFullPath = $CSV.FullName
$SheetName = ($CSV.name -split "\.")[0]
$worksheet = $worksheets.Item($CSVSheet)
$worksheet.Name = $SheetName
$TxtConnector = ("TEXT;" + $CSVFullPath)
$CellRef = $worksheet.Range("A1")
$Connector = $worksheet.QueryTables.add($TxtConnector,$CellRef)
$worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
$worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
$worksheet.QueryTables.item($Connector.name).Refresh()
$worksheet.QueryTables.item($Connector.name).delete()
$worksheet.UsedRange.EntireColumn.AutoFit()
$CSVSheet++
}
$workbooks.SaveAs($XLOutput,51)
$workbooks.Saved = $true
$workbooks.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbooks) | Out-Null
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}

While running through powershell_ise.exe, it is running fine and I am receiving the email successfully
This is because the ISE on your box is able to load your custom function during runtime and generate the report.
But when this is scheduled through a job, the script will run on a different server than your box, hence the script will not be able to find the function and you don't get the report.
I have faced this kind of issue before while using custom functions.
The work around that i can suggest is wrapping for custom functions in a separate module and importing the module in your main script. ( preferably save the module in the same location as your script for easy troubleshooting).
Example:
Save your function in a .psm1 module file
Function ScriptExample {
Param ( [string] $script,
[string] $jobname,
[string] $jobcategory,
[hashtable] $config,
[string] $deletelogfilepath,
[string] $servername)
#your-function-here#
{
}
Return $script;
}
Now call this module in your main script as follows,
$importmoduleroot = "C:\temp\SO_Example.psm1"
###### LOAD MODULES ######
# Import all related modules written to support this process
$modules = get-childitem -path $importmoduleroot -include SO*.psm1 -recurse;
You can then call your function and pass in the parameters within the main script,
ScriptExample -script $script `
-jobname $jobname `
-jobcategory $jobcategory `
-config $config `
-servername $ServerName `
-deletelogfilepath $deletelogfilepath

Related

Powershell Array -> HTML as string not as .Length

Currently I have a script that does a bunch of things, one thing is that it checks a .txt file to get all the current servers and then just does a ping to check connectivity before it does the rest of the script. I currently have that setup to add the servers it could not ping into an array so that it will not slow down the rest of the process attempting on failed servers. At the end of the script I have it all converted to an HTML document for ease of viewing. I would like to add the servers that failed the connection to the end of the HTML document to show that those servers failed.
As the script is now, it just prints the .Length property of the array I put those servers in. Here is the code I have that sets up the array, adds servers to it, and then the convertto-html part.
$servers = Get-Content "path\to\text.txt"
$failedConn = New-Object 'System.Collections.Generic.List[System.Object]'
foreach ($server in $servers)
{
$pingResult = Get-Ciminstance -ClassName win32_pingstatus -Filter "address='$server' and timeout=1000"
if ($pingResult.StatusCode -eq 0)
{
Write-Host "$server ping successful
}
else
{
Write-Host "$server ping Fail" -ForegroundColor Red
$failedConn.Add($server)
<# Also tried $failedConn += ($server) with the same error #>
}
}
<# Code for a forloop to do tasks where it adds an HTML variable called CombinedBody styling the tables whatnot #>
$CombinedBody += $failedConn | ConvertTo-Html | Out-File -Append C:\Path\To\html.html
The result just puts the failed connections at the very bottom of the HTML document, but it prints it as the length of each server name. How do I get it to print the actual server name? Any help is greatly appreciated!!
Length is the only property in the String object that ConvertTo-Html sees so that's what gets output. As a workaround, you can wrap the server names in another object that only have a single property containing the name, then it should output the actual names. Like this:
$failedConn | foreach{[pscustomobject]#{"Failed Servers" = $_}} | ConvertTo-Html | Out-File -Append C:\Path\To\html.html
Note that I've removed $CombinedBody += since Out-File doesn't return any output so that won't do anything.

Powershell: Save a workbook with SaveAs and a variable in file name

I have this strange problem with a script, I hope someone can help.
Thce script take data from a csv, open a workbook and put the data in place with a replacement, it continue do this till there's data to take, the problem is that I'd like to save the file with one of the variables I took from the .csv.
The script work itself but when the file is saved the variable was not taken and it continue going to create the same filename requesting me if I want to replace it.
here the script
Import-Csv C:\AZRconf\Batch.csv | ForEach-Object {
$site = $_.site
$uds = $_.uds
$jms = $_.jms
$objExcel = New-Object -com Excel.Application
$wb = $objExcel.Workbooks.Open("C:\AZRconf\Automation_Configuration_2.23.2.0_NS\ConfigurationAutomationNS.csv")
$sheet = $wb.ActiveSheet
[void]$sheet.Cells.Replace("XXX", "$site", [Microsoft.Office.Interop.Excel.XlLookAt]::xlPart)
[void]$sheet.Cells.Replace("YYY.ZZZ.YYY.ZZZ", "$uds", [Microsoft.Office.Interop.Excel.XlLookAt]::xlPart)
[void]$sheet.Cells.Replace("############", "$jms", [Microsoft.Office.Interop.Excel.XlLookAt]::xlPart)
$path = "C:\AZRconf\TempNS\ConfigurationAutomation_NS_($site).csv"
$wb.SaveAs($Path)
$objExcel.Quit()
}
and the script try to save the file as C:\AZRconf\TempNS\ConfigurationAutomation_NS_().csv, asking for any loop if I want to replace it with a different file with the same filename.
so, it don't take the $site variable.
Any hint?

Powershell SMO: This method does not support scripting data

I want to generate an import script for a MSSQL DB via Powershell (related to this question).
I tried doing this:
#Set-ExecutionPolicy RemoteSigned
$DB_NAME = "<<dbName>>"
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
$srv = new-object "Microsoft.SqlServer.Management.SMO.Server" "<<server>>"
$conContext = $srv.ConnectionContext
$conContext.LoginSecure = $false
$conContext.Login = "<<user>>"
$conContext.Password = "<<password>>"
$srv = new-object Microsoft.SqlServer.Management.Smo.Server($conContext)
$srv.SetDefaultInitFields([Microsoft.SqlServer.Management.SMO.View], "IsSystemObject")
$db = $srv.databases[$DB_NAME]
$scripter = new-object "Microsoft.SqlServer.Management.Smo.Scripter" $srv
$scripter.Options.ScriptSchema = $false
$scripter.Options.ScriptData = $true
$scripter.Options.ScriptDrops = $false
$scripter.Script($db)
But executing this throws an error:
"This method does not support scripting data"
I also tried to set the output file option but this doesn't change anything.
Can you tell me what I did wrong?
Thanks!
Per the error, Scripter.Script does not support scripting data. This is documented. What isn't documented is what you're supposed to use instead, but it's EnumScript:
$scripter.EnumScript(#($db.Tables))
You must pass the tables, since simply scripting the database will yield nothing (as, technically, the database itself contains no data, its tables do).
(The #() forcibly converts the Tables collection to an array, since that's what EnumScript expects.)

How to reference Script Arguments in DSC Script Resource

I'm using TFS 2015 Update 2 to create a release. One of my release steps is a "PowerShell on Target Machines" task that I'm using to apply a DSC configuration.
I'd like to use the Script Arguments field to pass in parameters from TFS to the DSC script.
My script looks like this:
Param(
[string]$data
)
configuration ApplyConfig
{
Script Backup {
SetScript = {
#do some stuff with $data
}
TestScript = {
Write-Output "Print param"
Write-Output $data
return $true
}
GetScript = {
return #{"Test" = "test data"}
}
}
}
ApplyConfig
The Script Arguments field contains this:
-Destination "$(ApplicationPath)"
However, at this point, $data seems to always be null. How can I get the argument defined in the Script Arguments field into my Script Resource?
When you reference $data in the TestScript you need the 'using' scope:
TestScript = {
Write-Output "Print param"
Write-Output $using:data
return $true
}
The TestScript executes on a different PowerShell context; 'using' allows you to copy the value of $data across those contexts.
My recommendation for flexibility is to declare a configuration hash table in your DSC script and pass parameters in to configure it. My Continuous Delivery with TFS / VSTS – Server Configuration and Application Deployment with Release Management blog post has a complete walkthrough of how to use DSC and Release Management in TFS 2015 Update 2.
Getting the parameters in then becomes a case of declaring your parameters as follows:
param(
[Parameter(Position=1)]
[string]$myFirstParameter,
[Parameter(Position=2)]
[string]$mySecondParameter
)
and then passing in the value in either directly:
Script Arguments field contains 'myFirstValue' 'mySecondValue'
or better as variables:
Script Arguments field contains $(myFirstValue) $(mySecondValue)

How do I get the location of a file to use in my PowerShell script?

I've been trying to figure this out for a while, but no luck, I would like to pop up a file open dialog, which I have been able to do with a function I found online, and then use the file location further down the line in my script, but I can't figure it out.
Here is the function
Function Get-FileName()
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = "C:\Users\$($env:username)\Documents\"
$OpenFileDialog.filter = "CSV's (*.csv)| *.csv"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
}
I'm new to powershell so I'm not sure how functions work.
Keep it in a variable and use it whenever you like.
$location = Get-FileName
copy $location c:\temp
start $location

Resources