I have the below powershell script which runs from jenkins against windows server 2019 slave:
$sqlpackagepublish = Start-Process -FilePath sqlpackage.exe -ArgumentList '/Action:Publish','/SourceFile:"Database Services\bin\Release\Database Services.dacpac"',"/TargetConnectionString:""Data Source=${Env};Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=False;Initial catalog=${Target}""","/p:BlockOnPossibleDataLoss=${Data_loss}" -wait -PassThru -Credential $Cred -RedirectStandardOutput sqlstdout.txt -RedirectStandardError sqlstderr.txt
$sqlpackagepublish.WaitForExit()
$sqlpackagepublish
if ($sqlpackagepublish.ExitCode -eq 0) {
Get-Content sqlstdout.txt
}
else {
echo "An error occurred"
Get-Content sqlstderr.txt
exit $sqlpackagepublish.ExitCode
}
But the deploy fails with no error in sqlstderr.txt and no info in jenkins log. any idea how I can debug it?
Update
based on the suggested answer below, I've tried both approaches:
1.
Remove a -PassThru parameter and read files' content.
So I changed my code the the below:
$sqlpackagepublish = Start-Process -FilePath sqlpackage.exe -ArgumentList '/Action:Publish','/SourceFile:"Database Services\bin\Release\Database Services.dacpac"',"/TargetConnectionString:""Data Source=${Env};Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=False;Initial catalog=${Target}""","/p:BlockOnPossibleDataLoss=${Data_loss}" -wait -Credential $Cred
$sqlpackagepublish.WaitForExit()
$sqlpackagepublish
But now I'm getting:
You cannot call a method on a null-valued expression.
+ $sqlpackagepublish.WaitForExit()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
robust .NET'y way
In my original question, I had these lines:
$Username = $args[0]
$Password = $args[1]
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
$sqlpackagepublish = Start-Process -FilePath sqlpackage.exe -ArgumentList {args} -wait -PassThru -Credential $Cred
I didn't understand how to add it to your code
This is how Start-Process command was basically created. -PassThru switch redirects the output to an object ($sqlpackagepublish in this case).
More on Start-Process here: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process?view=powershell-5.1
There are few solutions.
Remove a -PassThru parameter and read files' content as you are doing it right now
Do it harder, but more robust .NET'y way:
function Start-ProcessExecution
{
param (
[string] $exe,
[string] $arguments,
[string] $user,
[string] $password
)
$startInfo = New-Object System.Diagnostics.ProcessStartInfo;
$pass = ConvertTo-SecureString -AsPlainText $password -Force
$startInfo.UserName = "$user";
$startInfo.Password = "$pass";
$startInfo.FileName = $exe;
$startInfo.Arguments = $arguments;
$startInfo.UseShellExecute = $false;
$startInfo.RedirectStandardOutput = $true;
$startInfo.RedirectStandardError = $true;
$process = New-Object System.Diagnostics.Process;
$process.StartInfo = $startInfo;
$process.Start() | Out-Null;
$output = $process.StandardOutput.ReadToEnd();
$err = $process.StandardError.ReadToEnd();
$process.WaitForExit();
$obj = [PSCustomObject]#{
ExitCode = $process.ExitCode
StdOut = $output
StdErr = $err
}
return $obj;
}
$exe = "sqlpackage.exe"
$arguments = [string]::Join(" ", "/Action:Publish", `
'/SourceFile:"Database Services\bin\Release\Database Services.dacpac"', `
'/TargetConnectionString:"Data Source=${Env};Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=False;Initial catalog=${Target}"', `
'/p:BlockOnPossibleDataLoss=${Data_loss}')
$result = (Start-ProcessExecution -exe $exe -arguments $arguments -user $args[0] -password $args[1])
$ec = $result.ExitCode;
if ($ec -eq 0) {
Write-Host "STDOUT:`n$($result.StdOut)";
}
else {
Write-Host "STDERR:`n$($result.StdErr)";
}
Wrapper is basically there to better readability and is constructed to prevent the deadlock on reading huge outputs after the WaitForExit() has been called.
Related
I'm new to any sort of programming at all and I've been working on this tool, but I've finally hit a wall. I've read every article, post, and watched every video I could find but runspaces just aren't making sense to me. All I'm trying to do is get the auto search function to run on separate thread to keep the main window from locking up. I've been stuck on this for so long now that I considered ditching Powershell to start learning Python, but I'd rather understand this than quit. I think I even understand how to set the function to run on a separate runspace but the specific part that I don't understand is how to run the UI in it's own runspace and have the two communicate. I've even tried using some boilerplate with a syncHash table but I could never get it working. I'd really appreciate any help before I go insane.
All Files:
https://mega.nz/folder/251yHaBJ#bHYNYdNfAmia5mEhE5IOKQ
Powershell script in question:
#Xaml import
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$xamlFile = "MainWindow.xaml"
$inputXAML = Get-Content -Path $xamlFile -Raw
$inputXAML = $inputXAML -replace 'mc:Ignorable="d"','' -replace "x:N","N" -replace '^<Win.*','<Window'
[XML]$XAML = $inputXAML
$reader = New-Object System.Xml.XmlNodeReader $XAML
try {
$psform = [Windows.Markup.XamlReader]::Load($reader)
}
catch {
Write-Host $_.Exception
throw
}
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {
try {
Set-Variable -Name "var_$($_.Name)" -Value $psform.FindName($_.Name) -ErrorAction Stop
}
catch {
throw
}
}
#Functions
function autoSearch {
$Drives = Get-PSDrive -PSProvider "FileSystem"
$a1 = foreach ($Drive in $Drives) {Get-ChildItem -Path $Drive.Root -Recurse -ErrorAction SilentlyContinue -Directory -Filter "Steam"}
$a2 = Get-ChildItem (Join-Path $a1.FullName "userdata") -ErrorAction SilentlyContinue -Directory
$a3 = Get-ChildItem (Join-Path $a2.FullName "*") -ErrorAction SilentlyContinue -Directory -Filter "1446780"
if ($a3) {
$var_saveR.Foreground = "Green"
$var_saveR.Content = "Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 3000
}
$timer.add_Tick({$var_saveR.Content = ""})
$var_loc1.Content = $a3
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $a3.FullName + "\*"
$fileContent | Set-Content $filePath -Force
}
else {
$var_saveR.Foreground = "Red"
$var_saveR.Content = "Not Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 7000
}
$timer.add_Tick({$var_saveR.Content = ""})
}
}
function manualSave {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc1.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc1.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function manualBack {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc2.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "2"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc2.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function backup {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[0] ($a1[1] -replace '\\\*','') -Recurse
$var_backupStatus.Foreground = "Green"
$var_backupStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_backupStatus.Content = ""})
}
function restore {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[1] ($a1[0] -replace '\\\*','') -Recurse #-Confirm
$var_restoreStatus.Foreground = "Green"
$var_restoreStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_restoreStatus.Content = ""})
}
#UI
$var_aSearchButton.Add_Click({autoSearch})
$var_mSetButton1.Add_Click({manualSave})
$var_mSetButton2.Add_Click({manualBack})
$var_backupButton.Add_Click({backup})
$var_restoreButton.Add_Click({restore})
$var_loc1.Content = (Get-Content "Config.txt" -TotalCount 1) -replace '\\\*',''
$var_loc2.Content = (Get-Content "Config.txt" -TotalCount 2)[-1] -replace '\\\*',''
$psform.ShowDialog() | Out-Null
I want to copy database within the same server to have a test database using the under code but it works fine the first run and then an error occur .I think that was a problem of the name of the destination database because i change the name of destination it works also .How can I proceed to override the destination database without renaming the destination.
Import-Module SQLPS -DisableNameChecking
#your SQL Server Instance Name
$SQLInstanceName = "DESKTOP-444"
$Server = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -ArgumentList $SQLInstanceName
#provide your database name which you want to copy
$SourceDBName = "test"
#create SMO handle to your database
$SourceDB = $Server.Databases[$SourceDBName]
#create a database to hold the copy of your source database
$CopyDBName = "$($SourceDBName)_copy"
$CopyDB = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Database -ArgumentList $Server , $CopyDBName
$CopyDB.Create()
#Use SMO Transfer Class by specifying source database
#you can specify properties you want either brought over or excluded, when the copy happens
$ObjTransfer = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Transfer -ArgumentList $SourceDB
$ObjTransfer.CopyAllTables = $true
$ObjTransfer.Options.WithDependencies = $true
$ObjTransfer.Options.ContinueScriptingOnError = $true
$ObjTransfer.DestinationDatabase = $CopyDBName
$ObjTransfer.DestinationServer = $Server.Name
$ObjTransfer.DestinationLoginSecure = $true
$ObjTransfer.CopySchema = $true
#if you wish to just generate the copy script
#just script out the transfer
$ObjTransfer.ScriptTransfer()
#When you are ready to bring the data and schema over,
#you can use the TransferData method
$ObjTransfer.TransferData()
I was able to run your code multiple times without any issues. The following is the slightly cleaned-up version (structural changes):
Import-Module SQLPS -DisableNameChecking
$SQLInstanceName = "(local)"
$SourceDBName = "sandbox"
$CopyDBName = "${SourceDBName}_copy"
$Server = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Server' -ArgumentList $SQLInstanceName
$SourceDB = $Server.Databases[$SourceDBName]
$CopyDB = New-Object -TypeName 'Microsoft.SqlServer.Management.SMO.Database' -ArgumentList $Server , $CopyDBName
$CopyDB.Create()
$ObjTransfer = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Transfer -ArgumentList $SourceDB
$ObjTransfer.CopyAllTables = $true
$ObjTransfer.Options.WithDependencies = $true
$ObjTransfer.Options.ContinueScriptingOnError = $true
$ObjTransfer.DestinationDatabase = $CopyDBName
$ObjTransfer.DestinationServer = $Server.Name
$ObjTransfer.DestinationLoginSecure = $true
$ObjTransfer.CopySchema = $true
$ObjTransfer.ScriptTransfer()
$ObjTransfer.TransferData()
What error did you get?
The one thing I noticed. If the cloned database already exists, the script will fail. You should get an exception up around the $CopyDB.Create() statement and probably another one when you go to copy the objects to the cloned database.
I'd either drop the database if it exists, or abort script execution if it exists.
EDIT
I was told to use the module SqlServer instead of the module SQLPS, because the latter had long been deprecated. And immediately after I have made the change, I noticed that it was now possible to create databases from a Microsoft.SqlServer.Management.SMO.Transfer object, which I was not managing before. I don't understand why, and it might even be unrelated and I was just lucky. The SqlServer package can be installed through the following command:
Install-Module -Name SqlServer -AllowClobber
Thus I am updating my answer with the working code, which is more readable, more elegant and more performant than my previous answer (at the bottom of this post).
$SQLInstanceName = $env:servername
$SourceDBName = $env:databasename
$SQLUser = $env:adminlogin
$SQLPassword = $env:adminPassword
Import-Module SqlServer -DisableNameChecking
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;
Function IsNullOrEmpty([string]$val){
if ($val -eq $null -or $val -eq '') { $true }
else{ $false }
}
If (IsNullOrEmpty($SQLInstanceName)) {
$SQLInstanceName = $args[0]
}
If (IsNullOrEmpty($SourceDBName)) {
$SourceDBName = $args[1]
}
If (IsNullOrEmpty($SQLUser)) {
$SQLUser = $args[2]
}
If (IsNullOrEmpty($SQLPassword)) {
$SQLPassword = $args[3]
}
Try {
$Server = New-Object Microsoft.SqlServer.Management.Smo.Server($SQLInstanceName)
$DestinationDBName = "${SourceDBName}.Staging"
$SQLSecurePassword = ConvertTo-SecureString $SQLPassword -AsPlainText -Force
$Server.ConnectionContext.LoginSecure = $false
$Server.ConnectionContext.set_Login($SQLUser)
$Server.ConnectionContext.set_SecurePassword($SQLSecurePassword)
$SourceDB = $Server.Databases[$SourceDBName]
$ObjTransfer = New-Object Microsoft.SqlServer.Management.SMO.Transfer ($SourceDB)
$CopyDB = New-Object Microsoft.SqlServer.Management.SMO.Database ($Server, $DestinationDBName)
$CopyDB.Create()
# $ObjTransfer.CopyData = $false - Uncomment this line so that data is not copied across
$ObjTransfer.CopySchema = $true
$ObjTransfer.CopyAllTables = $true
$ObjTransfer.CopyAllDatabaseTriggers = $true
$ObjTransfer.Options.WithDependencies = $true
$ObjTransfer.Options.ContinueScriptingOnError = $true
$ObjTransfer.DestinationDatabase = $DestinationDBName
$ObjTransfer.DestinationServer = $SQLInstanceName
$ObjTransfer.DestinationPassword = $SQLPassword
$ObjTransfer.DestinationLogin = $SQLUser
$ObjTransfer.DestinationLoginSecure = $false
$ObjTransfer.TransferData()
}
Catch [System.Exception] {
# $_ is set to the ErrorRecord of the exception
if ($_.Exception.InnerException) {
Write-Error $_.Exception.InnerException.Message
} else {
Write-Error $_.Exception.Message
}
if($Server.Databases.Name -like $DestinationDBName) {
Write-Host "Dropping cloned database..."
# Call drop-db.ps1 to delete the stagingDB
Invoke-Command { .\drop-db.ps1 $SQLInstanceName $DestinationDBName $SQLUser $SQLPassword }
}
}
Finally {
if($Server) {
$Server.ConnectionContext.Disconnect()
}
}
I was having a similar error implementing this. Tried literally everything, it just wouldn't work. What did work for me, was generating a script through the ScriptTransfer method, create the new database and then apply the script to the new database through Invoke-SqlCmd. The code I am sharing can be invoked locally, by passing 4 arguments to the script in the following order:
Server Name
Database Name
Login
Password
And it can also be used on a pipeline. I am using it on Azure DevOps by setting those 4 arguments through a group variable.
I am appending .Staging to the source database name, and that's the name I give to the new database. If something fails along the way, I delete the new database, in case it has already been created.
$SQLInstanceName = $env:servername
$SourceDBName = $env:databasename
$SQLUser = $env:adminlogin
$SQLPassword = $env:adminPassword
Import-Module SQLPS -DisableNameChecking
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;
Function IsNullOrEmpty([string]$val){
if ($val -eq $null -or $val -eq '') { $true }
else{ $false }
}
If (IsNullOrEmpty($SQLInstanceName)) {
$SQLInstanceName = $args[0]
}
If (IsNullOrEmpty($SourceDBName)) {
$SourceDBName = $args[1]
}
If (IsNullOrEmpty($SQLUser)) {
$SQLUser = $args[2]
}
If (IsNullOrEmpty($SQLPassword)) {
$SQLPassword = $args[3]
}
Try {
$Server = New-Object Microsoft.SqlServer.Management.Smo.Server($SQLInstanceName)
}
Catch [System.Exception] {
# $_ is set to the ErrorRecord of the exception
if ($_.Exception.InnerException) {
Write-Error $_.Exception.InnerException.Message
} else {
Write-Error $_.Exception.Message
}
}
Finally {
Try {
$StagingDBName = "${SourceDBName}.Staging"
$SQLSecurePassword = ConvertTo-SecureString $SQLPassword -AsPlainText -Force
$Server.ConnectionContext.LoginSecure = $false
$Server.ConnectionContext.set_Login($SQLUser)
$Server.ConnectionContext.set_SecurePassword($SQLSecurePassword)
$CreationScriptOptions = New-Object Microsoft.SqlServer.Management.SMO.ScriptingOptions
$CreationScriptOptions.ExtendedProperties= $true
$CreationScriptOptions.DRIAll= $true
$CreationScriptOptions.Indexes= $true
$CreationScriptOptions.Triggers= $true $CreationScriptOptions.ScriptBatchTerminator = $true
$CreationScriptOptions.IncludeHeaders = $true;
$CreationScriptOptions.ToFileOnly = $true
$CreationScriptOptions.IncludeIfNotExists = $true
$SourceDB = $Server.Databases[$SourceDBName]
$ObjTransfer = New-Object Microsoft.SqlServer.Management.SMO.Transfer ($SourceDB)
$ObjTransfer.options=$CreationScriptOptions # tell the transfer object of our preferences
$FilePath = Join-Path $PSScriptRoot "$($StagingDBName).sql"
$ObjTransfer.Options.Filename = $FilePath;
$ObjTransfer.ScriptTransfer()
$CopyDB = New-Object Microsoft.SqlServer.Management.SMO.Database ($Server, $StagingDBName)
$CopyDB.Create()
$auth=#{UserName=$SQLUser;Password=$SQLPassword}
Invoke-SqlCmd -InputFile $FilePath -ServerInstance $Server -Database $StagingDBName #Auth -Verbose
}
Catch [System.Exception] {
# $_ is set to the ErrorRecord of the exception
if ($_.Exception.InnerException) {
Write-Error $_.Exception.InnerException.Message
} else {
Write-Error $_.Exception.Message
}
if($Server.Databases.Name -like $StagingDBName) {
Write-Host "Dropping staging database..."
$auth=#{UserName=$SQLUser;Password=$SQLPassword}
Invoke-SqlCmd -ServerInstance $Server #Auth `
-Query "IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name ='$($StagingDBName)') `
BEGIN `
ALTER DATABASE [$($StagingDBName)] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; `
DROP DATABASE [$($StagingDBName)]; `
END;" `
-Verbose
}
}
Finally {
$Server.ConnectionContext.Disconnect()
}
}
I'm trying to make an automated script that automatically directs to a
secure website in IE, enters login credentials, and goes to a database tab
and a database link to download. This will require no user interaction and
be initiated through windows task scheduler.
However, it works about 75% of the time. I am new to
powershell and made novice mistakes, so any help or direction would
greatly be appreciated. Thank you!
PowerShell.exe -windowstyle hidden {
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
Write-Output "$(Get-TimeStamp) Script Executed $dc" | Out-file C:\Users
\JohnSmith\Desktop\Script\ScriptLog.txt -append
$username = "admin"
$password = "admin123"
$ie = new-object -com InternetExplorer.Application
#Navigate to the login page
$ie.navigate("Login Page")
#Wait for the page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$ie.visible = $true #comment this line after debugging
#Assigning DOM to $doc variable
$doc = $ie.document
try {
$usernameField = $doc.getElementById('userName')
#write-host $usernameField
$usernameField.value = $username
write-host $username
$passwordField = $doc.getElementById('password')
$passwordField.value = $password
write-host $pass
#Find and click the submit button
$submitButton = $doc.getElementById('login')
write-host $submitButton
$submitButton.click()
#Wait until login is complete
do {sleep 1} until (-not ($ie.Busy))
} catch {$null}
do {sleep 1} until (-not ($ie.Busy))
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$ie = new-object -com internetexplorer.application
$ie.visible=$true
$ie.navigate('Database Download' )
while($ie.busy) {sleep 1}
$link = $ie.Document.getElementsByTagName('A') | where-object
{$_.innerText -eq 'Download the Complete Database'}
$link.click()
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("{TAB}");
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like '*Internet Explorer*'
} | ForEach-Object
{
$_.Quit()
[Runtime.Interopservices.Marshal]::ReleaseComObject($_)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
Here was my finished script. It's not as elegant as I would like it to be, but it does what I need it to do, for now! Thank you!
PowerShell.exe -windowstyle hidden {
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
#Creates appending log file for everytime script runs
Write-Output "$(Get-TimeStamp) Script Executed $dc" | Out-file C:\Users
\JohnSmith\Desktop\DatabaseScript\DatabaseScriptLog.txt -append
$username = "admin"
$password = "admin123"
#write-host $pass
#Create the IE com object
$ie = new-object -com InternetExplorer.Application
#Navigate to the login page
Start-Process 'https://www.LoginPageToTheSecureSite'
<#$ie.navigate("https://www.LoginPageToTheSecureSite")
#Wait for the page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$ie.visible = $true
#Assign the DOM to the $doc variable
$doc = $ie.document
try {
#Find the username field and set the value to that of variable
$usernameField = $doc.getElementById('User ID')
#write-host $usernameField
$usernameField.value = $username
write-host $username
#Find the password field and set the value to that of the result
#of a call to the get-password function with the parameter defined at
top
$passwordField = $doc.getElementById('Password')
$passwordField.value = $password
write-host $pass
#Submit button
$submitButton = $doc.getElementById('Login')
write-host $submitButton
$submitButton.click()
#Wait until login is complete
do {sleep 1} until (-not ($ie.Busy))
} catch {$null}
#Wait for page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
Start-Process 'https://www.DownloadTheDatabaseSelectionTab'
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
Start-Process 'https://www.DownloadThisAsZipFile.zip'
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Opening Database Zip File')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("{TAB 2}");
#Closes every instance of IE
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like '*Internet Explorer*'
} | ForEach-Object {
$_.Quit()
}
#Releases COM Object, cleans up.
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like 'Internet Explorer'
} | ForEach-Object {
$_.Quit()
[Runtime.Interopservices.Marshal]::ReleaseComObject($_)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
I am making a script that goes into all servers we're hosting and gets all members of a specific group and the domain name, and then exports it to a file. I'm saving the users and the domain names into two arrays AA (user array) and DA (domain array) AA stands for användararray, and "användare" is users in swedish so it makes sense to me.
I noticed that the export step didn't work, no users or domain names were exported, so I tried to print them in the function. But it doesn't print anything, so I tried to print it in a different location (didn't work). After some experimenting I came to the conlusion that the only place the arrays actually contains any information is inside the foreach loop where I save the users that I find??!
Here is the code
unction GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
#$DA + ";" + $Anvandare.Name
$DA + ";" + $AA
}
}
$output
}
}
$DA
$AA
}
function Export {
Write-Host("C")
$filsökväg = "C:\Users\322sien\Desktop\Coolkids.csv"
$ColForetag = "Företag"
$ColAnvandare = "Användare"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $DomainArray.Length; $p++) {
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
$DA + $delimiter + $AA | Out-File $filsökväg -Append
}
}
ReadInfo
GetData
Export
Can anyone help me with this? I've sat down with this all day and i cant find a solution.
Your variables $DA and $AA are bound to GetData function, so they live only there. You could make them available inside your script by changing it's scope.
Change this:
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
To this:
$script:AA = #($Anvandare.Name)
$script:DA = gc env:UserDomain
So they will now be available for other functions inside the script.
Also I found the ways to improve your script, hope you can see the logic:
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
[array]$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
$array = #()
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$object = New-Object PSObject
$object | Add-Member -MemberType NoteProperty -Name AA -Value #($Anvandare.Name)
$object | Add-Member -MemberType NoteProperty -Name DA -Value (gc env:UserDomain)
$object | Add-Member -MemberType NoteProperty -Name Something -Value $DA + ";" + $AA
$array += $object
}
Write-Output $array
}
Write-Output $output
}
}
}
Your function will now output some data.
I'm working on a PowerShell script to run a query against multiple servers and databases were the idea is to dynamically add server and databases to an array and execute them.
Currently I'm stuck at the last part where everything is combined. I can add the servers but not the databases.
What I am trying to achieve: PowerShell script with MFP GUI to run a query against multiple MSSQL servers which all contain identical database (with different data) but the databases have different names like Sql_Data-Node1, SqlData-Node2, etc.
Problem I encounter: I managed to add the servers dynamically to an array and when I run a query I get the proper response. In this case I used the master database an I made it static (-database 'master'). When I try to do the same with the databases (add them to an array) I get an error:
Invoke-Sqlcmd : Cannot validate argument on parameter 'Database'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At C:\Users\master\Documents\MULTSCRIPT\MultiQueryV0.6.ps1:368 char:109
+ ... ame -Password $PassWord -ServerInstance $_[0] -Database $_[1] -Query ...
+ ~~~~~
+ CategoryInfo : InvalidData: (:) [Invoke-Sqlcmd], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
My code:
#Create EMPTY ARRAY for Databases
$script:DBSet = New-Object System.Collections.ArrayList
#==========================================================================
$window.Master.add_Checked({
$script:Master = 'master' #Add IP to Variable
$script:DBSet.Add("$script:Master") #Add Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.Master.add_Unchecked({
$script:Master = $null
$script:DBSet.Remove("$script:Master")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#==========================================================================
$window.DataNodes.add_Checked({
$script:DB01 = 'Database01'
$script:DBSet.Add("$script:DB01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DataNodes.add_Unchecked({
$script:DB01 = $null
$script:DBSet.Remove("$script:DB01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#Create EMPTY ARRAY For Servers
$script:ServerAddress = New-Object System.Collections.ArrayList
#Add action to Checkbox====================================================
$window.DB00.add_Checked({
$script:SRV00 = '190.168.1.8' #Add IP to Variable
$script:ServerAddress.Add("$script:SRV00") #Add Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DB00.add_Unchecked({
$script:SRV00 = $null
$script:ServerAddress.Remove("$script:SRV00") #Remove Variable to Array
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#==========================================================================
$window.DB01.add_Checked({
$script:SRV01 = '192.168.1.9'
$script:ServerAddress.Add("$script:SRV01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
$window.DB01.add_Unchecked({
$script:SRV01 = $null
$script:ServerAddress.Remove("$script:SRV01")
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
#Collect Credentials#======================================================
$credential = Get-Credential
$UserName = $credential.UserName.Replace('\','')
$PassWord = $credential.GetNetworkCredential().password
#Collect From Input Fields#================================================
$window.Button.add_Click({
$SQLQuery = $window.Query.Text.ToString()
$Server = $script:ServerAddress
$DatabaseSet = $script:DBSet
$instances = #( #($Server, $DatabaseSet) )
$instances | ForEach{
Invoke-Sqlcmd -AbortOnError `
-Username $UserName`
-Password $PassWord`
-ServerInstance $_[0]`
-Database $_[1]`
-Query $SQLQuery`
-QueryTimeout 30 |
Out-GridView -Title $_[0]
}
[System.Object]$sender = $args[0]
[System.Windows.RoutedEventArgs]$e = $args[1]
})
It seems that the following solution works.
Credit goes to:Mutiple Variables in Foreach Loop [Powershell]
$window.Button.add_Click(
{ $DataBase = $window.DataBase.Text.ToString()
$SQLQuery = $window.Query.Text.ToString()
$Server = $ServerAddress.getenumerator()
$Database = $DBSet.getenumerator()
while($Server.MoveNext() -and $Database.MoveNext()){
Invoke-Sqlcmd -AbortOnError -Username $UserName -Password $PassWord -ServerInstance $Server.Current -Database $Database.Current -Query $SQLQuery -QueryTimeout 30 | Out-GridView -Title $Server.Current}