Check if file is in use before exporting - loops

I'm writing a script that will export an excel file to a PDF. I got that part working, However, because I'm saving on top of an existing PDF it cannot be open when the export happens. I'm looking for a way to have PowerShell check if the file is currently open, and if so, wait for X seconds and then check again. If not it can proceed.
It currently works perfectly and breaks if the PDF is open, however I need it to loop.
Here's what I have so far:
$path = "c:\users\XXXXX\documents"
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$excelFiles = Get-ChildItem -Path $path -Include spreadsheet.xlsx -Recurse
$File = "c:\users\XXXXX\documents\Exported.pdf"
try {
[IO.File]::OpenWrite($File).Close();
$true
} catch {
break
}
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
foreach ($wb in $excelFiles) {
$filepath = Join-Path -Path $path -ChildPath ($wb.BaseName + ".pdf")
$workbook = $objExcel.Workbooks.Open($wb.FullName, 3)
$workbook.Saved = $true
"saving $filepath"
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath)
$objExcel.Workbooks.Close()
}
$objExcel.Quit()

This should do what you want; thanks to Ben Baird for the Test-FileLock function.
function Test-FileLock {
param ([parameter(Mandatory=$true)][string]$Path)
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false)
{
return $false
}
try
{
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream)
{
$oStream.Close()
}
# file is unlocked.
$false
}
catch
{
# file is locked by a process.
return $true
}
}
$path = "c:\users\XXXXX\documents"
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$excelFiles = Get-ChildItem -Path $path -Include spreadsheet.xlsx -Recurse
$File = "c:\users\XXXXX\documents\Exported.pdf"
while((Test-FileLock $file) -eq $true)
{
Start-Sleep -Seconds 3
}
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
foreach ($wb in $excelFiles) {
$filepath = Join-Path -Path $path -ChildPath ($wb.BaseName + ".pdf")
$workbook = $objExcel.Workbooks.Open($wb.FullName, 3)
$workbook.Saved = $true
"saving $filepath"
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath)
$objExcel.Workbooks.Close()
}
$objExcel.Quit()
The code will check for a file lock and if it is detected wait 3 seconds and then try again. Once the lock is cleared the PDF export code will run.

Related

Need Help Understanding Runspaces with WPF

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

How to close form GUI after checking existing file in PowerShell?

I want to check an existing file, if the process still waiting for the file, it will display a GUI window. After the file is exist, the window will close automatically.
I tried this code, the window can not close, even the file already exist.
Checking the file:
$SN = "708TSTA"
$MAC = "2E5961370"
function Find {
$n = 0
while (-not (Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})) {
Start-Sleep -s 1
D:\Auto\GUI.ps1
$n++
(Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})
Write-Host "Attempt no $n"
}
Write-Host ">>Flag found after $n attempts"
return $true
}
if (Find) {
Write-Host "Found"
}
GUI.ps1:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Check = Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"}
if($Check) {
Write-Host "File Exist"
$Form.Close()
}
$Form.Controls.AddRange(#($Label1,$Label2))
[void]$Form.ShowDialog()
Instead of doing Start-Sleep inside the GUI, it is better to use a timer so the form stays responsive.
I changed the code of the GUI.ps1 (not the way it looks) like this:
Param (
[string]$Path = '*.*',
[string]$MaxAttempts = 5
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# set things up for the timer
$script:nAttempts = 0
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # 1 second
$timer.Add_Tick({
$global:Result = $null
$script:nAttempts++
$file = Get-Item -Path $Path
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
elseif ($script:nAttempts -ge $MaxAttempts) {
$global:Result = [PSCustomObject]#{
Exists = $false
FileName = ''
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
})
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Form.Controls.AddRange(#($Label1,$Label2))
# start the timer as soon as the dialog is visible
$Form.Add_Shown({ $timer.Start() })
[void]$Form.ShowDialog()
# clean up when done
$Form.Dispose()
And to call it from your other script, use:
$SN = "708TSTA"
$MAC = "2E5961370"
function Test-FileExists {
$file = Get-Item -Path "D:\*$SN-$MAC*"
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = 1
}
}
else {
& "D:\GUI.ps1" -Path "D:\*$SN-$MAC*" -MaxAttempts 3
}
}
# call the function that can call the GUI.ps1 script
Test-FileExists
# check the Global result object
if ($global:Result.Exists) {
Write-Host "File '$($global:Result.FileName)' Exists. Found after $($global:Result.Attempts) attempts." -ForegroundColor Green
}
else {
Write-Host "File not found after $($global:Result.Attempts) attempts." -ForegroundColor Red
}
Update
As per your comments, I understand that the calling script should show the form (which does nothing more that show on screen) AND is responsible for closing it after the file has been found.
The code below should do what you ask by defining the $Form as a global variable and by using the .Show() method of the form instead of ShowDialog():
GUI.ps1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$global:Form = New-Object System.Windows.Forms.Form
$global:Form.ClientSize = '578,400'
$global:Form.Text = "Form"
$global:Form.BackColor = "#c1daf7"
$global:Form.WindowState = 'Maximized'
$global:Form.FormBorderStyle = "FixedDialog"
$global:Form.ControlBox = $false # hide sizing and close buttons
$global:Form.TopMost = $true
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$global:Form.Controls.AddRange(#($Label1,$Label2))
# don't use ShowDialog() here because it will block the calling script
$global:Form.Show()
the calling script
function Test-FileExists {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[string]$Path,
[string]$Pattern = '*.*'
)
$nAttempts = 1
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
if (!$file) {
# show the GUI
& "D:\GUI.ps1"
do {
Start-Sleep -Seconds 1
$nAttempts++
Write-Verbose "Attempt No. $nAttempts"
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
} until ($file)
# clean up the form
$global:Form.Dispose()
$global:Form = $null
}
Write-Verbose "File '$($file.FullName)' Exists. Found after $nAttempts attempt(s)."
return $true
}
$SN = "708TSTA"
$MAC = "2E5961370"
# call the function that can call the GUI.ps1 script
if (Test-FileExists -Path 'D:\SERVER\SHARE' -Pattern "*$SN-$MAC*" -Verbose) {
Write-Host "Found"
}
Hope that helps

Copy SQL Server database with PowerShell script

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()
}
}

Array removed after function?

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.

PowerShell nested loop for renaming files in directory

I am trying to do a nested loop in PowerShell. I am renaming images in a several sub directories but i need the loop to reset based on it finding a new directory.
Here is the code I wrote but I can't get it to work as it gives me an error saying the file doesn't exist.
function rename-cruise {
Param([string]$parkname, [string]$extension=".jpg")
if(!$parkname){
$park = $args[0];
} else {
$park = $parkname;
}
$dirs = Get-ChildItem -Recurse | ?{ $_.PSIsContainer };
foreach($dir in $dirs){
if($dir -cnotlike "177"){
$i = 0;
$currentDir = get-location;
$location = "$currentDir\$dir";
write-host $currentDir
write-host $location
Get-ChildItem -Path $location -Filter "*$extension" |
ForEach-Object {
if($i -lt 10){
$newName = "$park-0$i$extension";
Rename-Item $_.Name -NewName $newName;
} else {
$newName = "$park-$i$extension";
Rename-Item $_.Name -NewName $newName;
}
$i++;
}
}
}
}

Resources