PowerShell Using **DacServices** With SQLCMD Variables To Deploy A DACPAC - powershell

In PowerShell I'm using Microsoft.SqlServer.Dac.DacServices and Microsoft.SqlServer.Dac.DacDeployOptions to deploy/update a database DACPAC. The problem I am having is finding where to set the SQLCMD Variables the package requires.
Abbreviated Sample
# Create a DacServices object, which needs a connection string
$dacsvcs = New-Object Microsoft.SqlServer.Dac.DacServices "server=$sqlserver"
# Load dacpac from file
$dp = [Microsoft.SqlServer.Dac.DacPackage]::Load($dacpac)
# Deploy options
$deployOptions = New-Object Microsoft.SqlServer.Dac.DacDeployOptions
$deployOptions.IncludeCompositeObjects = $true
I know I can input these just fine with SqlPackage.exe, and maybe that's what I should do. But no where in the documentation or web grok can I find an example of DacServices usage with SQLCMD variables as an option--SQLCMD variables as required parameters for my project's DACPAC.

You should set options in the $deployOptions.SqlCommandVariableValues property. This is an updateabase Dictionary - you can't assign a new dictionary but you can update the key/value pairs inside it. For example to set a variable "MyDatabaseRef" to "Database123" use
$deployOptions.SqlCommandVariableValues.Add("MyDatabaseRef", "Database123");
The API reference is here.

I have another code snippet to share in relation to this, a method of processing multiple variables from a Powershell script argument;
param(
[hashtable] $SqlCmdVar
)
$deployOptions = New-Object Microsoft.SqlServer.Dac.DacDeployOptions
# Process the Sql Command Variables
#
if ($SqlCmdVar -ne $null)
{
foreach($key in $SqlCmdVar.keys)
{
Write-Verbose -Message "Adding Sql Command Variable ""$key""..."
$deployOptions.SqlCommandVariableValues.Add($key,$SqlCmdVar[$key])
}
}
You would call the script like this;
myscript.ps1 -SqlCmdVar #{ variable1 = "my first value"; variable2 = "my second value"; variableetc = "more values"}

Related

it is possible to pass argument to powershell script a datatable?

I'm trying to create a PowerShell script that inserts a datatable to SQL via WriteToServer...
This script is called by a PowerAutomateDesktop automation.
So... I cannot pass my datatable as an argument :(
%dt% it s datatable variable which needs to be used inside powershell script.
This is my dilemma - it is interpreted as a string or something like that
#Invoke-sqlcmd Connection string parameters
$params = #{'server'='SQLEXPRESS';'Database'='Db'}
Write-Output %dt%
#Variable to hold output as data-table
$dataTable = %dt% | Out-DataTable
#Define Connection string
$connectionString = "Data Source=DSQLEXPRESS; Integrated Security=SSPI;Initial Catalog=Db"
#Bulk copy object instantiation
$bulkCopy = new-object ("Data.SqlClient.SqlBulkCopy") $connectionString
#Define the destination table
$bulkCopy.DestinationTableName = "dbo.__SALES"
#load the data into the target
$bulkCopy.WriteToServer($dataTable)
#Query the target table to see for output
Invoke-Sqlcmd #params -Query "SELECT * FROM dbo.__SALES" | format-table -AutoSize
Thanks!
UPDATE
No loner need to pass an argument - I create the datatable inside the script.
Thanks again!
Work-around: create the datatable inside the script

Powershell Splatting Object Attribute (Typeof System.Collections.Hashtable)

Going to give an example to make it clearer what I want to do
$AzLogin = #{
Subscription = [string] 'SubscriptionID';
Tenant = [string] 'tenantID';
Credential = [System.Management.Automation.PSCredential] $credsServicePrincipal;
ServicePrincipal = $true;
}
try{
Connect-Azaccount #$AzLogin -errorAction Stop
}catch{
Write-Host "Error: $($_.exception)" -foregroundcolor red
}
This works correctly.
The one I want to do is pass splatted arguments stored in property 'CommonArgs' of object 'CSObject', something like this:
$CSObject =# {
[PScustomObject]#{CommonArgs=$AzLogin;}
}
try{
Connect-Azaccount #CSObject.commonArgs -errorAction Stop
}catch{
Write-Host "Error: $($_.exception)" -foregroundcolor red
}
You can only splat a variable as a whole, not an expression that returns a property value - as of PowerShell 7.1
However, there's an approved RFC that would allow for expression-based splatting; there is no specific time frame for its implementation, though; community members are welcome to contribute.
A variable used for splatting may only contain a hashtable (containing parameter-name-and-argument pairs, as in your question) or an array (containing positional arguments), not a [pscustomobject] - see about_Splatting.
Something like the following should work:
# Note: It is $CSObject as a whole that is a [pscustomobject] instance,
# whereas the value of its .CommonArgs property is assumed to be
# a *hashtable* (the one to use for splatting).
$CSObject = [pscustomobject] #{
CommonArgs = $AzLogin # assumes that $AzLogin is a *hashtable*
}
# Need a separate variable containing just the hashtable
# in order to be able to use it for splatting.
$varForSplatting = $CSObject.CommonArgs
Connect-Azaccount #varForSplatting -errorAction Stop

Powershell Access Azure DevOps Secret Variables

I am attempting to read an Azure DevOps secret variable from a Powershell pipeline script. The variable looks like this within Azure:
I've attempted to access the secret variable both as a param such as
[CmdletBinding()]
Param (
$SecurePassword = $env:Password
)
and simply as an environment variable such as
$SecurePassword = $env:Password
Unfortunately the variable continues to appear null using either method.
I have no issue accessing non-secret variables. Any help would be greatly appreciated.
---------------------------------------- EDIT ----------------------------------------
I found documentation here stating that secrets are available to scripts within the pipeline if explicitly mapped in the environment section of the task.
I've updated my Powershell task and attempted to map the variable as both $(Password) and Password without any luck.
Mapping $(Password) as above reveals the string hidden behind asterisks.
We had a requirement to create a new project in Azure DevOps and needed to migrate all of the pipelines to the new project. Lo and behold, no one knew all of the secrets, and export / import doesn't accomplish this.
I wrote a script to output all environment variables into an "Extensions" tab next to the build summary. It's formatted and everything.
The key to outputting the secret is by altering the string by inserting the '<-eliminate->' phrase within the secret value and saving to a file. Once the file is created, we then remove all instances of the string '<-eliminate->', save the file, and there it sits as an extension page to the build summary.
I would like to somehow find All secrets dynamically, but for now manually defining the variable name does the trick.
I re-formatted for this post and removed proprietary info, please let me know if it's broken :)
function GetSecretLength ($secretVar){
$i = 0;
while($true){
try {
$secretVar.substring(0,$i)|out-null
} catch {
break
};
$i++;
}
if ($i -le 1) { return 1 }
else { return $i-1 };
}
function GetSecret($secret){
$length = GetSecretLength($secret);
if ($length -ge 2) {
return $secret.substring(0,$length-1 )+"<-eliminate->"+$secret.substring($length-1,1)
} elseif ($length -eq 1) {
return $secret+"<-eliminate->"
} else {
return ""
}
}
$var = (gci env:*).GetEnumerator() | Sort-Object Name
$out = ""
Foreach ($v in $var) { $out = $out + "`t{0,-28} = {1,-28}`n" -f $v.Name, (GetSecret($v.Value)) }
$fileName = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\build-variables.md"
write-output "dump variables on $fileName"
set-content $fileName $out
write-output "##vso[task.addattachment type=Distributedtask.Core.Summary;name=Environment Variables;]$fileName"
((Get-Content -path $fileName -Raw) -replace '<-eliminate->', '') | Set-Content -Path $fileName
You have to add the secret variables that you want into the "Environment Variables" of the Powershell task:
You end up with this pretty tab:
Why not just store the password in a keyvault as a secret? then there are azure commands for accessing the secret, and avoid all this. Heck, we generate random passwords, store them in keyvaults, then access the contents in the appropriate resource, without ever needing to expose the decrypted secret in a powershell command, like in an ARM template for an azure sql server database.
I know this doesn't solve your initial question, but it is a workaround that does work.

Send variable number of key value pairs from CLI

I'm needing to execute a PowerShell script as part of my TFS build pipeline. The PowerShell script is generic and executes a given AWS Cloud Formation template given to it. I need the developer to supply the template with a list of key/value pairs that represent the templates parameters. Since they can use this to execute any Cloud Formation template, the input parameters will vary.
How can I create an input parameter that is key/value based that I can pass as a parameter to another PowerShell object that accepts a Hashmap of parameters?
The following pseudo code is what I'm trying to achieve
param(
[Parameter(Mandatory=$true)][string]$environment,
[KeyValuePair[]]$templateParameters
)
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters #( $templateParameters)
I can explicitly create the parameters and pass them in like this:
$bucketNameParameter = new-object Amazon.CloudFormation.Model.Parameter
$bucketNameParameter.ParameterKey = "bucketname"
$bucketNameParameter.ParameterValue = "FooBar"
$isVersionedParameter = new-object Amazon.CloudFormation.Model.Parameter
$isVersionedParameter.ParameterKey = "bucketname"
$isVersionedParameter.ParameterValue = "FooBar"
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters #( $environmentParameter, #isVersionedParameter )
Since each template has completely different parameters they can take, I would like to make this script flexible to facilitate re-use. What is the most PowerShell way of approaching that?
You can accept a [hashtable] instance and create your [Amazon.CloudFormation.Model.Parameter] instances based on its entries:
param(
[Parameter(Mandatory=$true)] [string] $environment,
[hashtable] $templateParameters
)
# Convert the hashtable's entries to an array of
# [Amazon.CloudFormation.Model.Parameter] instances.
$params = $templateParameters.GetEnumerator() | ForEach-Object {
$param = New-Object Amazon.CloudFormation.Model.Parameter
$param.ParameterKey = $_.Key
$param.ParameterValue = $_.Value
$param # output
}
New-CFNStack -StackName $stackName -TemplateURL $fullTemplateUrlPath -Parameters $params
Note the use of .GetEnumerator(), which is necessary in order to enumerate the hashtable's entries and send them through the pipeline; by default, PowerShell sends hashtables as a whole through the pipeline.
Using your (modified-to-be-unique) example values, you'd invoke your script as:
./script.ps1 -environment foo `
-templateParameters #{ bucketName1 = 'FooBar1'; bucketName2 = 'FooBar2' }

PowerShell DSC - how to pass configuration parameters to ScriptResources?

I'm having a lot of trouble trying to get a PowerShell Desired State Configuration script working to configure an in-house application. The root of the problem is that I can't seem to pass my configuration data down into a ScriptResource (at least not with the way I'm trying to do it).
My script is supposed to create a config folder for our in-house application, and then write some settings into a file:
configuration MyApp {
param (
[string[]] $ComputerName = $env:ComputerName
)
node $ComputerName {
File ConfigurationFolder {
Type = "Directory"
DestinationPath = $Node.ConfigFolder
Ensure = "Present"
}
Script ConfigurationFile {
SetScript = {
write-verbose "running ConfigurationFile.SetScript";
write-verbose "folder = $($Node.ConfigFolder)";
write-verbose "filename = $($Node.ConfigFile)";
[System.IO.File]::WriteAllText($Node.ConfigFile, "enabled=" + $Node.Enabled);
}
TestScript = {
write-verbose "running ConfigurationFile.TestScript";
write-verbose "folder = $($Node.ConfigFolder)";
write-verbose "filename = $($Node.ConfigFile)";
return (Test-Path $Node.ConfigFile);
}
GetScript = { #{Configured = (Test-Path $Node.ConfigFile)} }
DependsOn = "[File]ConfigurationFolder"
}
}
}
For reference, my configuration data looks like this:
$config = #{
AllNodes = #(
#{
NodeName = "*"
ConfigFolder = "C:\myapp\config"
ConfigFile = "C:\myapp\config\config.txt"
}
#{
NodeName = "ServerA"
Enabled = "true"
}
#{
NodeName = "ServerB"
Enabled = "false"
}
)
}
And I'm applying DSC with the following:
$mof = MyApp -ConfigurationData $config;
Start-DscConfiguration MyApp –Wait –Verbose;
When I apply this configuration it happily creates the folder, but fails to do anything with the config file. Looking at the output below, it's obvious that it's because the $Node variable is null inside the scope of ConfigurationFile / TestScript, but I've got no idea how to reference it from within that block.
LCM: [ Start Resource ] [[Script]ConfigurationFile]
LCM: [ Start Test ] [[Script]ConfigurationFile]
[[Script]ConfigurationFile] running ConfigurationFile.TestScript
[[Script]ConfigurationFile] node is null = True
[[Script]ConfigurationFile] folder =
[[Script]ConfigurationFile] filename =
LCM: [ End Test ] [[Script]ConfigurationFile] in 0.4850 seconds.
I've burnt off an entire day searching online for this specific problem, but all the examples of variables, parameters and configuration data all use File and Registry resources or other non-script resources, which I've already got working in the "ConfigurationFolder" block in my script. The thing I'm stuck on is how to reference the configuration data from within a Script resource like my "ConfigurationFile".
I've drawn a complete blank so any help would be greatly appreciated. If all else fails I may have to create a separate "configuration" script per server and hard-code the values, which I really don't want to do if at all possible.
Cheers,
Mike
Change this: $Node.ConfigFolder to $using:Node.ConfigFolder.
If you have a variable called $Foo and you want it to be passed to a script DSC resource, then use $using:Foo
Based on David's answer, I've written a utility function which converts my script block to a string and then performs a very naive search and replace to expand out references to the configuration data as follows.
function Format-DscScriptBlock()
{
param(
[parameter(Mandatory=$true)]
[System.Collections.Hashtable] $node,
[parameter(Mandatory=$true)]
[System.Management.Automation.ScriptBlock] $scriptBlock
)
$result = $scriptBlock.ToString();
foreach( $key in $node.Keys )
{
$result = $result.Replace("`$Node.$key", $node[$key]);
}
return $result;
}
My SetScript then becomes:
SetScript = Format-DscScriptBlock -Node $Node -ScriptBlock {
write-verbose "running ConfigurationFile.SetScript";
write-verbose "folder = $Node.ConfigFolder";
write-verbose "filename = $Node.ConfigFile)";
[System.IO.File]::WriteAllText("$Node.ConfigFile", "enabled=" + $Node.Enabled);
}
You have to be mindful of quotes and escapes in your configuration data because Format-DscScriptBlock only performs literal substitution, but this was good enough for my purposes.
A quite elegant way to solve this problem is to work with the regular {0} placeholders. By applying the -f operator the placeholders can be replaced with their actual values.
The only downside with this method is that you cannot use the curly braces { } for anything other than placeholders (i.e. say a hashtable or a for-loop), because the -f operator requires the braces to contain an integer.
Your code then looks like this:
SetScript = ({
Set-ItemProperty "IIS:\AppPools\{0}" "managedRuntimeVersion" "v4.0"
Set-ItemProperty "IIS:\AppPools\{0}" "managedPipelineMode" 1 # 0 = Integrated, 1 = Classic
} -f #($ApplicationPoolName))
Also, a good way to find out if you're doing it right is by simply viewing the generated .mof file with a text editor; if you look at the generated TestScript / GetScript / SetScript members, you'll see that the code fragment really is a string. The $placeholder values should already have been replaced there.
ConfigurationData only exists at the time the MOF files are compiled, not at runtime when the DSC engine applies your scripts. The SetScript, GetScript, and TestScript attributes of the Script resource are actually strings, not script blocks.
It's possible to generate those script strings (with all of the required data from your ConfigurationData already expanded), but you have to be careful to use escapes, subexpressions and quotation marks correctly.
I posted a brief example of this over on the original TechNet thread at http://social.technet.microsoft.com/Forums/en-US/2eb97d67-f1fb-4857-8840-de9c4cb9cae0/dsc-configuration-data-for-script-resources?forum=winserverpowershell