PowerShell issue when using string variable from web.config in concatenation - powershell

I have this issue with a string ($dataBaseName variable) I'm pulling from a web.config file via a function.
When I Write-Host it displays the value, and when I use GetType() it shows that it is a System.String, but it will not pull the value when concatenating with another variable. Eventually, I want to use it as a parameter to call from the module I'm writing.
Here is the code I use to get the $dataBaseName variable I'm having trouble with.
# Get WebConfig Detail
$configPath = Get-ItemProperty 'HKLM:\SOFTWARE\Wow6432NodeItem\Key\Settings' `
| Select #{Name="Path";Expression={$_.installPath + "\Item\Web.config" }} `
-ExpandProperty Path
$config = [xml](Get-Content $configPath)
function Get-WebConfigKey($key) {
$config.SelectSingleNode("//appSettings/add[#key='$key']").Value
}
# Set Database Variable
$databaseName = Get-WebConfigKey "databaseName"
Later I try to reference it by combining it with the computer variable using many different ways to concatenate it using (with (), string.format, etc.), but it will only display "ComputerName\" for the value of the $instance variable.
$computer = Get-Content env:computername
$instance = $computer + "\" + $databaseName
If I hard-code the database name it works fine.
$instance = $computer + "\" + "exampleDatabase"
I'm using the information to an SQL query as below. I'm actually going to use a secure string for the password later, but this is for reference.
$computer = Get-Content env:computername
$instance = $computer + "\" + $databaseName
$server = new-object ('Microsoft.SqlServer.Management.Smo.Server') $instance
$server.ConnectionContext.LoginSecure=$false
$server.ConnectionContext.set_Login("sa")
$server.ConnectionContext.set_Password("examplePassword")
$db = $server.Databases.Item("Maduro")
[String] $sql = "SELECT SomeRow FROM SomeTalbe;"
$result = $db.ExecuteWithResults($sql)
$table = $result.Tables[0]
foreach ($row in $table) {
$someVarible = $row.SomeRow
}
Web.config excerpt:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<!--NEXT GEN EF DB INFO-->
<add key="providerName" value="System.Data.SqlClient" />
<add key="serverName" value="LOCALHOST\Instance" />
<add key="databaseName" value="database" />
</appSettings>
</configuration>

Related

Proccessing hashtable values correctly inside a ForEach-Object

I have a .xml file that I want to use to create windows .url files out of:
<?xml version="1.0" encoding="UTF-16" ?>
<items_list>
<item>
<title>About topics - PowerShell | Microsoft Learn</title>
<url>https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about?view=powershell-7.3</url>
</item>
<item>
<title>PowerShell HashTable - Everything you need to know — LazyAdmin</title>
<url>https://lazyadmin.nl/powershell/powershell-hashtable/</url>
</item>
<item>
<title>How a Regex Engine Works Internally</title>
<url>https://www.regular-expressions.info/engine.html</url>
</item>
</items_list>
I placed the Titles and URLs into a Hashtable:
$InXML = [XML](Get-Content .\test.xml)
$BookMarks = [ordered]#{
Title = (Select-Xml -xml $InXML -XPath "//title" | % {$_.Node.InnerXml}) -replace '\?|\\|/|:'
URL = Select-Xml -xml $InXML -XPath "//url" | % {$_.Node.InnerXml}
}
Everything good so far, then I start running into problems when I try to loop through the Titles and URLs:
$wshshell = New-Object -ComObject WScript.Shell
$BookMarks | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $shortcutFilePath
# $shortcutFilePath = Join-Path -path "c:\temp" -ChildPath "$Title.Url"
# $shortcut = $shell.CreateShortcut($shortcutFilePath)
# $shortcut.TargetPath = "$Url"
# $shortcut.Save()
}
I commented out my actuall code in the Loop to see whats actually happening with Write-Output. All the key values get concatenated into one long title or one long .url
What I am expecting is to get a single pair of a Url and Title at a time, so that I can create the .url file in the loop.
Reading the documentations and articles on how to do this. I tried variations of:
$BookMarks.Values | ForEach-Object {
$Title = $_.Title
$Url = $_.Url
Write-Output $Title
Write-Output $Url
}
I keep getting null variables.
Any help or ideas would be really appreciated.
I suggest simplifying and streamlining your code as follows, which bypasses your problem:
[xml] $inXML = Get-Content -Raw .\test.xml
$inXml.items_list.item | ForEach-Object {
$title = $_.Title
$url = $_.Url
"[$title] [$url]" # sample output.
# ... work with $title and $url here
# $wshshell = New-Object -ComObject WScript.Shell
# ...
}
The above uses PowerShell's adaptation of the XML DOM, which allows you to access elements and attributes as if they were properties.

Select SFTP connection data for WinSCP .NET assembly from CSV file based on user input

I have this PowerShell code.
Works good. But when I add more lines to db.csv, it doesn't work and return error code:
Exception calling "Open" with "1" argument(s): "Connection has been unexpectedly closed. Server sent command exit status 0.
Authentication log (see session log for details):
Using username "username".
Authentication failed."
At C:\Users\me\Desktop\testeScript\CollectLog.ps1:41 char:5
+ $session.Open($sessionOptions)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SessionRemoteException
When I have for example only two lines in db.csv the script works very well
HostName,IP
name1,10.10.1.1
name2,10.10.1.2
I try with 19 hostname and Ip addr line in CSV doc and works, but when I add only 1 more stopping works.
19 works
20+ doesn't work..
Any idea? (Thank you and sorry for my english)
Add-Type -Path "WinSCPnet.dll"
$Name = #()
$ip = #()
Import-Csv db.csv |`
ForEach-Object {
$Name += $_.HostName
$ip += $_.IP
}
$inputID = Read-Host -Prompt "Type ID"
if ($Name -contains $inputID)
{
$Where = [array]::IndexOf($Name, $inputID)
Write-Host "IP: " $ip[$Where]
}
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "$ip"
UserName = "username"
Password = "password"
GiveUpSecurityAndAcceptAnySshHostKey = "true"
}
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.GetFiles("/C:/Program Files/Common Files/logs/Device.log", "E:\loguri\Log\Arhive\*").Check()
}
finally
{
$session.Dispose()
}
Compress-Archive -Path "E:\loguri\Log\Arhive\Device.log" -DestinationPath "E:\loguri\Log\Arhive\$inputID.zip" -Update
Remove-Item -Path "E:\loguri\Log\Arhive\Device.log" -Force
as Theo says you have no need to separate out your CSV into two arrays as you have. Import it like this
$db = import-csv 'db.csv'
You can access each row as $db[0], $db[1] and each column from your CSV will be a property, e.g. $db[0].Hostname and $db[0].IP
After you have read in your input you just need to select the entry from the array $db. Perhaps like this. However neither your code nor mine covers the case where is no match!
$entry = $db -match $inputID
Then your session will be defined like this
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Scp
HostName = $entry.ip
UserName = "username"
Password = "password"
GiveUpSecurityAndAcceptAnySshHostKey = "true"
}
With all that said, given the error message that you have it would appear that the combination of username/password and ip are not valid.
Add-Type -Path "WinSCPnet.dll"
$db = import-csv 'db.csv'
$inputID = Read-Host -Prompt "Type ID"
$entry = $db -match $inputID
# Set up session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Sftp
HostName = $entry.IP
UserName = "username"
Password = "password"
GiveUpSecurityAndAcceptAnySshHostKey = "true"
}
$session = New-Object WinSCP.Session
try {
# Connect
$session.Open($sessionOptions)
# Transfer files
$session.GetFiles("/C:/Program Files/Common Files/logs/Device.log", "E:\loguri\Log\Arhive\*").Check()
}
finally {
$session.Dispose()
}
if (Test-Path "E:\loguri\Log\Arhive\Device.log") {
Compress-Archive -Path "E:\loguri\Log\Arhive\Device.log" -DestinationPath "E:\loguri\Log\Arhive\$inputID.zip" -Update
Remove-Item -Path "E:\loguri\Log\Arhive\Device.log" -Force
}

Using WinSCP in PowerShell to compare FTP to local directories

Trying to use WinSCP and PowerShell to list the remote and local directories comparing the lowest level to then transfer the directories on the remote missing from the local.
Listing works for both remote and local but the comparison -contains operator shows false even though when I Write-Host for $fileInfo it looks like they should match. I tried adding $fileInfo to an array for the comparison but it has much more than just the lowest directory like the attributes and other stuff for the directories.
$localpath = "C:\Users"
$remotePath = "/home"
Add-Type -Path "WinSCPnet.dll"
$localfolders = Get-ChildItem $localpath
$localtargets = #()
$remotetargets = #()
foreach ($f in $localfolders){
$split = $f.FullName -split '\\'
$localtargets += $split[$split.Count-1]
}
$sessionOptions = New-Object WinSCP.SessionOptions -Property #{
Protocol = [WinSCP.Protocol]::Ftp
HostName = "ftp.com"
UserName = "user"
Password = "password"
}
$session = New-Object WinSCP.Session
try
{
$session.Open($sessionOptions)
$fileInfos = $session.ListDirectory($remotePath)
foreach ($fileInfo in $fileInfos.Files)
{
Write-Host $localtargets.Contains($fileInfo)
$remotetargets += $fileInfo | Out-String
}
}
As I understand $localtargets is just a list of strings representig file names, so you should just test if it contains the name of the file, in your last loop, can you test :
Write-Host $localtargets.Contains($fileInfo.Name)
The post by #JPBlack correctly answers your literal question.
Though note that WinSCP .NET assembly can synchronize your folder on its own using Session.SynchronizeDirectories:
$session.Open($sessionOptions)
$session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, $localpath, $remotePath, $False).Check()
Or if you just want to find the differences, use the Session.CompareDirectories.

Create printers from an imported csv file to multiple print servers

I have been working with this script and have successfully grabbed info from a .csv file and added it to one print server.
Right now I have the print server hard coded in the script and it allows me to add multiple print servers into the script, but I would like to add the print servers to a column in my .csv file and read from there to eliminate the static servers in the code. Here is what I have:
The second part I am struggling with is publishing and not publishing printers ( listing in AD or not ) I was thinking of adding another column called published. Then creating an if/then to publish or not publish**
foreach ($server in #("printserver1")) {
foreach ($printer in #(Import-Csv C:\PrinterList.csv)) {
Add-PrinterPort -ComputerName $server -Name $printer.IPAddress -PrinterHostAddress $printer.IPAddress
Add-Printer -ComputerName $server -Name $printer.Printername -DriverName $printer.Driver -PortName $printer.IPAddress -Comment $printer.Comment -Location $printer.Location -Shared -ShareName $printer.Printername -Published
}
}
If PrinterList.csv contains a column called Publish with False or True as possible values, you can do the following:
foreach ($printer in (Import-Csv C:\PrinterList.csv)) {
$Params = #{ ComputerName = $server
Name = $printer.Printername
DriverName = $printer.Driver
PortName = $printer.IPAddress
Comment = $printer.Comment
Location = $printer.Location
ShareName = $printer.Printername
}
Add-Printer #Params -Shared -Published:([bool]::Parse($printer.Publish))
}
Since Publish is a [switch] parameter, you can use the syntax -Publish:$true or -Publish:$false. The Parse() method parses a string value into a boolean value.
$Params Splatting is not necessary here. It just provides a bit more readability.
Alternatively, [System.Convert]::ToBoolean($printer.Publish) has the same result in the proposed scenario but does offer more flexibility as [System.Convert]::ToBoolean(0) returns False and [System.Convert]::ToBoolean(1) returns True.

Unable to create printers on Windows 2008 using WMI Class

When using the below code to create printer on Windows 2008 servers to create the printers
`function CreatePrinterPort {
$server = $args[0]
$port = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance()
$port.Name= $args[1]
$port.SNMPEnabled=$false
$port.Protocol=2
$port.HostAddress= $args[2]
$port.Put()
}
function CreatePrinter {
$server = $args[0]
$print = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_Printer”).createInstance()
$print.Drivername = $args[1]
$print.PortName = $args[2]
$print.Shared = $true
$print.Published = $false
$print.Sharename = $args[3]
$print.Location = $args[4]
$print.Comment = $args[5]
$print.DeviceID=$args[2]
$print.Put()
}
$printers = Import-Csv “C:\printers.csv”
foreach ($printer in $printers) {
CreatePrinterPort $printer.Printserver $printer.Portname $printer.IPAddress
CreatePrinter $printer.Printserver $printer.Driver $printer.Portname $printer.Sharename $printer.Location $printer.Comment $printer.Printername
}'
I am getting the following error. The port creation function is working.
"IsSingleton : False
Exception calling "Put" with "0" argument(s): "Generic failure "
At line:23 char:1
+ $print.Put()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException"
I have importing all the details from a CSV file and it contains all the information.
Any suggestions?
I'm pretty sure you're both doing this the hard way, and didn't read the MSDN page for the Win32_Printer class. It says in the remarks that you have to enable the SeDriverUpdate privilege before you can issue the Put method, so I have a feeling that's where you're getting your error from.
Next, use the Set-WMIInstance cmdlet, or the newer 'New-CIMInstance` if you can. Calling the classes directly is possible I'm sure, but if the server is local it won't enable the privileges that you need to create a printer.
Lastly, you could make your function better if you made it an advanced function, and allowed piped data. Check this out:
function CreatePrinter {
[cmdletbinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Printserver')]
$Server,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Driver')]
$Drivername,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$PortName,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Sharename,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Location,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Comment,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('IPAddress')]
$HostAddress,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('PrinterName')]
$Name
)
PROCESS{
$PortArgs = #{
Name = $PortName
SNMPEnabled = $false
Protocol = 2
HostAddress = $HostAddress
}
Set-WmiInstance -Class Win32_TCPIPPrinterPort -Arguments $PortArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
$PrinterArgs = #{
Drivername = $Drivername
PortName = $PortName
Shared = $true
Published = $false
Sharename = $Sharename
Location = $Location
Comment = $Comment
DeviceID = $PortName
Name = $Name
}
Set-WmiInstance -Class Win32_Printer -Arguments $CmdArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
}
}
That creates the port, and then the printer. I suppose you could split it into two functions, but do you really see needing one without the other? Plus you can pipe your CSV data directly into it like this:
Import-CSV "C:\printers.csv" | CreatePrinter
That's it, that will create ports and printers for all records in the CSV. Plus I used the UpdateOrCreate enum, so if something isn't right you could correct the CSV and just run it again to refresh the settings to the correct settings without having to worry about deleting old copies and making new copies of things.