I am having a problem when deploying cubes via PowerShell that I don’t know how to fix. In my solution I have some measures with FormatString: \£ #,##0;-\£ #,##0. If I deploy them normally via Visual Studio, the XMLA I pull from the SSAS server after deployment will have <FormatString>\£ #,##0;-\£ #,##0</FormatString> which is all good and expected. However, if I deploy via PowerShell, the resulting XMLA from the server is <FormatString>\£ #,##0;-\£ #,##0</FormatString>.
I realise the issue is with £ being parsed, but I am not sure how to resolve it.
The bit of background here is that I am using Octopus Deploy, and I've extracted the PowerShell below where the issue occurs:
$modelpath = "C:\...\Project_Name.xmla"
$Path_To_Microsoft_AnalysisServices_Deployment = "C:\Program Files (x86)\Microsoft SQL Server\140\Tools\Binn\ManagementStudio\Microsoft.AnalysisServices.Deployment.exe"
$SSAS_Project_Name = "Project_Name"
$Path_To_SSAS_Bin_Folder = "F:\Octopus Deploy\CustomInstallation\Project_Name\Content"
$FilePath_asdatabase = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.asdatabase"
$FilePath_deploymenttargets = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.deploymenttargets"
$FilePath_configsettings = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.configsettings"
$FilePath_deploymentoptions = "$Path_To_SSAS_Bin_Folder\$SSAS_Project_Name.deploymentoptions"
Start-Process -FilePath $Path_To_Microsoft_AnalysisServices_Deployment -ArgumentList "`"$FilePath_asdatabase`"","/s:`"$FilePath_DeploymentLog`"","/o:`"$SSAS_Project_Name.xmla`"","/d" -Wait -NoNewWindow
# Load the Adomd Client
[System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.AnalysisServices.AdomdClient”) | Out-Null;
# Connect to the server
Write-Host "Executing Deployment Script"
$serverName = “Data Source=.”
$conn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection $serverName
$conn.Open()
# Read XMLA
$xmla = Get-Content $modelpath
# Execute XMLA
$cmd = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdCommand $xmla, $conn
return $cmd.ExecuteNonQuery();
I have done some investigation and I’ve found the following:
The xmla file produced by PowerShell (not in the snippet) has the correct Format String \£ #,##0;-\£ #,##0
The .asdatabase file also has the correct format string
I suspect that something is happening either at:
Start-Process -FilePath $Path_To_Microsoft_AnalysisServices_Deployment -ArgumentList “`”$FilePath_asdatabase`””,”/s:`”$FilePath_DeploymentLog`””,”/o:`”$SSAS_Project_Name.xmla`””,”/d” -Wait -NoNewWindow
or further down at:
# Execute XMLA
$cmd = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdCommand $xmla, $conn
return $cmd.ExecuteNonQuery();
Any help would be appreciated.
PS: I’ve tried playing with the Format String when setting it in the solution but haven’t been able to find a solution. Note that I can’t simple use “Currency” b/c the server is set to English (US).
The answer to this is to specify the encoding. From Jeroen in the comment in response to my question:
It looks like PowerShell is auto-detecting the encoding of the XMLA incorrectly as Windows-1252, when it's actually UTF-8 (without a BOM). Try $xmla = Get-Content $modelpath -Encoding UTF8. It may also be the case that the XMLA itself is actually incorrect -- make sure to inspect it in an editor capable of reporting the encoding, like Notepad++, not just have it echo back by PowerShell itself. – Jeroen Mostert 15 mins ago
Related
I may be going about this the wrong/more difficult way. I am open to suggestions.
I am running NTLite v2.3.8.8920 [HOME] ((c) NTlitesoft d.o.o) to create unattended Windows 10 discs. After years of doing unattended discs and realizing the ever expanding size of the disc, (Latest disc was 32.73GB!), I found WinGet, the absolutely amazing repository, and have even gone as far as to create my own installer!
The issue for today is: how do I access WinGet during an unattended installation? I have compiled a list of applications that I use frequently; most that I have been hard coding to the disc and thus this incredible size; and I would love to be able to run this script post-setup and save the time and space. Here is my code:
#The first batch here is a function I created for notification purposes. Not sure how to do timed popups in Powershell yet.
#Get Words
function GW($myinput){
$WS = New-Object -ComObject "Wscript.Shell"
$ws.popup($myinput,3,'TK Installer',64)|SET-CLIPBOARD}
SET-CLIPBOARD to offload the popup response code. Need to find a better output or a way to prevent printing this response.
function install-myapps(){
Clear-Host
#Variable to hold the application list
$myapps = (
'Microsoft.PowerShell',
'Microsoft-Windows.Terminal',
'Microsoft.DotNet.SDK.3_1',
'Microsoft.DotNet.SDK.5',
'Microsoft.DotNet.SDK.6',
'Microsoft.MSIXCore',
'Microsoft.msmpisdk',
'Microsoft.ADKPEAddon',
'Microsoft.WebDeploy',
'9N5LW3JBCXKF',
'Nlitesoft.NTLite',
'Libretro.RetroArch',
'Notepad++.Notepad++',
'CodecGuide.K-LiteCodecPack.Full',
'Foxit.FoxitReader',
'7zip.7zip',
'OBSProject.OBSStudio',
'XnSoft.XnConvert',
'XnView.Classic',
'XnSoft.XnViewMP',
'corel.winzip',
'XP8K0J757HHRDW')
#Parser
ForEach-Object($aa in $myapps.Split(',')){
#Notification
GW "Installing $aa`nPlease wait..."
#Installer
WinGet install $aa --silent --accept-package-agreements --accept-source-agreements --force}
}
This code works perfectly in both command line and exe format; the latter using PS2EXE or IExpress. I just cannot figure out how to instantiate it post-setup from the unattended Win1021H2 side. Any help or insight would be greatly appreciated!
I was unable to figure out the process for this so I worked it around differently.
Below is how I fixed this situation:
# The first section opens and names function and
# declares variable $Hopeful applied to the full URI for the application we are installing
# !Considering using get-input but for now we will just use a replaceable variable!
# The second section begins the downloading and saving process
# Begins by separating the application from the URI assuming the format is as www.domain.com/application.exe
# Note now that the variable $JustApp will pull the just the last portion of the URI which is the application name
# Also, we'll make sure that what we're trying to do is possible by checking the extension of the last object
# Because wget needs 2 things; the URI and a place for the download to go; I am creating a directory to put these
# downloads in. Thus, $MyDir\$JustApp is now the default file point.
cls
Clear-Host
$MyDir = "d:\TKDI\"
# Create directory
if($mydir|Test-Path){
"My Directory Already Exists!"
}else{
md $MyDir -Force
}
# Section 1
Function TKDI($Hopeful,$MyArgs){
$_|select
# Section 2
$JustApp = $hopeful -split('/')|select -last 1
if($justapp -match "exe")
{
switch($MyArgs)
{
inno{$x ='/sp- /silent /forcecloseapplications /restartapplications /norestart'}
S{$x ='/S'}
silent{$x ='/silent'}
quiet{$x ='/quiet'}
passive{$x ='-passive'}
default{$x =$myargs}
un{$x ='-uninstall'}
$null{$x='/?'}
}
cls
echo "Processing $justapp"
if(Test-Path $mydir$justapp -PathType Leaf){echo 'File Downloaded Already!'}else{wget -Uri $hopeful -OutFile $MyDir$justapp}
$noteit = 'Installing $justapp in 5 seconds...'
$x=6;while($x-- -ge 1){cls;Write-host $x;sleep 1}
start -verb runas -wait -FilePath $mydir$justapp -ArgumentList $x
}elseif($justapp -match "msi")
{
cls
echo "You're file will be downloaded and installed!"
wget -Uri $hopeful -OutFile $MyDir$justapp
start -wait -Verb runas msiexec.exe -ArgumentList "-i $mydir$justapp /passive /norestart"
}else{
echo "This URI does not result in an application!"
}
}
tkdi www.example.com/index.exe inno #Installs beautifully```
I assembled a Powershell script that is designed to grab other scripts that are hosted on Azure blobs, and execute them.
The relevant code blocks:
Obtaining the script:
$resp = (Invoke-WebRequest -Uri $scriptUri -Method GET -ContentType "application/octet-stream;charset=utf-8")
$migrationScript = [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray());
$tempPath = Get-ScriptDirectory
$fileLocation = CreateTempFile $tempPath "migrationScript.ps1" $migrationScript
Creating the file:
$newFile = "$tempFolder"+"\"+"$fileName"
Write-Host "Creating temporary file $newFile"
[System.IO.File]::WriteAllText($newFile, $fileContents)
And then I invoke the downloaded file with
Invoke-Expression "& `"$fileLocation`" $migrationArgs"
This is working well, for what I need. However, the Invoke-Expression is not correctly reading the encoding of the file. It opens correctly in Notepad or Notepad++, but not in ISE (where I am executing the script right now).
Is there a way I can ensure the script is read correctly? It is necessary to support UTF8, as there is a possibility that the scripts will need to perform operations such as setting an AppSetting to a value that contains special characters.
EDIT: Behaviour is the same on "vanilla" non-ISE Powershell invocation.
As per #lit and #PetSerAI, the BOM is required for Powershell to work correctly.
My first attempt had not been successful, so I switched back to non-BOM, but, with the following steps, it worked:
Perform the Invoke-WebRequest with -ContentType "application/octet-stream;charset=utf-8"
Grab the Raw content (you will see it in Powershell as a series of numbers, which I assume are the ascii codes?) and convert its bytes with [system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray()); to an array containing the characters you want.
When saving the file via .NET's WriteAllText, ensure you use UTF8,
[System.IO.File]::WriteAllText($newFile, $fileContents, [System.Text.Encoding]::UTF8). In this case, UTF8 is understood to be UTF8 with a byte order mark, and is what Powershell needs.
EDITED: Here is my code now. The install file does copy to the remote host. However, the WMI portion does not install the .exe file, and no errors are returned. Perhaps this is a syntax error with WMI? Is there a way to just run the installer silently with PsExec? Thanks again for all the help sorry for the confusion:
#declare params
param (
[string]$finalCountdownPath = "",
[string]$slashes = "\\",
[string]$pathOnRemoteHost = "c:\temp\",
[string]$targetJavaComputer = "",
[string]$compname = "",
[string]$tempPathTarget = "\C$\temp\"
)
# user enters target host/computer
$targetJavaComputer = Read-Host "Enter the name of the computer on which you wish to install Java:"
[string]$compname = $slashes + $targetJavaComputer
[string]$finalCountdownPath = $compname + $tempPathTarget
#[string]$tempPathTarget2 =
#[string]$finalCountdownPath2 = $compname + $
# say copy install media to remote host
echo "Copying install file and running installer silently please wait..."
# create temp dir if does not exist, if exist copy install media
# if does not exist create dir, copy dummy file, copy install media
# either case will execute install of .exe via WMII
#[string]$finalCountdownPath = $compname + $tempPathTarget;
if ((Test-Path -Path $finalCountdownPath) )
{
copy c:\hdatools\java\jre-7u60-windows-i586.exe $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
else {
New-Item -Path $finalCountdownPath -type directory -Force
copy c:\hdatools\dummy.txt $finalCountdownPath
copy "c:\hdatools\java\jre-7u60-windows-i586.exe" $finalCountdownPath
([WMICLASS]"\\$targetJavaComputer\ROOT\CIMV2:win32_process").Create("cmd.exe /c c:\temp\java\jre-7u60-windows-i586.exe /s /v`" /qn")
}
I was trying to get $Job = Invoke-Command -Session $Session -Scriptblock $Script to allow me to copy files on a different server, because I needed to off load it from the server it was running from. I was using the PowerShell Copy-Item to do it. But the running PowerShell script waits until the file is done copying to return.
I want it to take as little resources as possible on the server that the powershell is running to spawn off the process on another server to copy the file. I tried to user various other schemes out there, but they didn't work or the way I needed them to work. (Seemed kind of kludgey or too complex to me.) Maybe some of them could have worked? But I found a solution that I like that works best for me, which is pretty easy. (Except for some of the back end configuration that may be needed if it is is not already setup.)
Background:
I am running a SQLServer Job which invokes Powershell to run a script which backups databases, copies backup files, and deletes older backup files, with parameters passed into it. Our server is configured to allow PowerShell to run and under the pre-setup User account with SQL Server Admin and dbo privileges in an Active Directory account to allow it to see various places on our Network as well.
But we don't want it to take the resources away from the main server. The PowerShell script that was to be run would backup the database Log file and then use the another server to asynchronously copy the file itself and not make the SQL Server Job/PowerShell wait for it. We wanted it to happen right after the backup.
Here is my new way, using WMI, using Windows Integrate Security:
$ComputerName = "kithhelpdesk"
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -Command "Copy-Item -Path \\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak -Destination \\YourShareDestination\YourDatabase_2018-08-07_11-45.log.bak"'
Here is my new way using passed in Credentials, and building arg list variable:
$Username = "YouDomain\YourDomainUser"
$Password = "P#ssw0rd27"
$ComputerName = "RemoteServerToRunOn"
$FromFile = "\\YourShareSource\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ToFile = "\\YourShareDestination\SQLBackup\YourDatabase_2018-08-07_11-45.log.bak"
$ArgumentList = 'powershell.exe -Command "Copy-Item -Path ' + $FromFile + ' -Destination ' + $ToFile + '"'
$SecurePassWord = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $Username, $SecurePassWord
([Wmiclass]'Win32_Process').GetMethodParameters('Create')
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList $ArgumentList -Credential $Cred
We think that this above one is the preferred one to use.
You can also run a specific powershell that will do what you want it to do (even passing in parameters to it):
Invoke-WmiMethod -ComputerName RemoteServerToRunOn -Path win32_process -Name create -ArgumentList 'powershell.exe -file "C:\PS\Test1.ps1"'
This example could be changed to pass in parameters to the Test1.ps1 PowerShell script to make it more flexible and reusable. And you may also want to pass in a Credential like we used in a previous example above.
Help configuring WMI:
I got the main gist of this working from: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1
But it may have also needed WMI configuration using:
https://helpcenter.gsx.com/hc/en-us/articles/202447926-How-to-Configure-Windows-Remote-PowerShell-Access-for-Non-Privileged-User-Accounts?flash_digest=bec1f6a29327161f08e1f2db77e64856b433cb5a
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enable-psremoting?view=powershell-5.1
Powershell New-PSSession Access Denied - Administrator Account
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/invoke-wmimethod?view=powershell-5.1 (I used to get how to call Invoke-WmiMethod).
https://learn.microsoft.com/en-us/powershell/scripting/core-powershell/console/powershell.exe-command-line-help?view=powershell-6 (I used to get syntax of command line)
I didn't use this one, but could have: How to execute a command in a remote computer?
I don't know for sure if all of the steps in the web articles above are needed, I suspect not. But I thought I was going to be using the Invoke-Command PowerShell statement to copy the files on a remote server, but left my changes from the articles above that I did intact mostly I believe.
You will need a dedicated User setup in Active Directory, and to configure the user accounts that SQL Server and SQL Server Agent are running under to give the main calling PowerShell the privileges needed to access the network and other things to, and can be used to run the PowerShell on the remote server as well. And you may need to configure SQLServer to allow SQL Server Jobs or Stored Procedures to be able to call PowerShell scripts like I did. But this is outside the scope of this post. You Google other places on the internet to show you how to do that.
I have a task to deploy two Windows services created using Topshelf to a test server as part of our continuous integration build process.
My MSBuild target file is as follows:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Condition="'$(ConfigurationName)'=='Release'" Name="StopService">
<Exec Command="powershell.exe -NonInteractive -executionpolicy Unrestricted -command "& { &'.\ServiceStop.ps1' } "" ContinueOnError="true" />
</Target>
</Project>
This executes a Powershell script (well, a couple of lines) situated within the same folder within the project called ServiceStop.ps:
$service = get-service -ComputerName MyServerName -Name 'MyServiceName'
stop-service -InputObject $service -Verbose
The problem
When I queue a new build from within TFS, the script does successfully execute; however, the get-service command fails to find the service in question - despite the fact that it is definitely there and running. The specific error from the build log is as follows:
Get-Service : Cannot find any service with service name 'MyServiceName' (TaskId:198)
When the script is run locally from my machine, the service on the remote machine is found and stopped successfully, making me think it is some sort of permissions issue.
What I've tried
I have very limited experience with Powershell. I read that credentials could be stored within a Powershell object like so:
$pw = Read-Host -AsSecureString "Enter password"
$pw | ConvertFrom-SecureString | Out-File -Path .\storedPassword.txt
$password = get-content .\storedPassword.txt | convertto-securestring
$credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist "myAdminAccountName",$password
However, it would appear that get-service does not have any method to pass credentials to it.
I also experimented with using PSExec to remotely start and stop the service, but ran into similar issues.
Some questions I reviewed
Using PowerShell credentials without being prompted for a password
Powershell stop-service error: cannot find any service with service name
Powershell Get-WmiObject Access is denied
Saving credentials for reuse by powershell and error ConvertTo-SecureString : Key not valid for use in specified state
I have spent more time on this issue than I can really afford, so would appreciate any help / guidance / comments that may help.
Thank you!
UPDATE
I was able to confirm that the Powershell script was receiving information from the MSBuild task, as the log showed Powershell output.
However, I ran out of time to find a solution and instead worked around the issue by dropping the updated services binaries onto the target server and wrote a Powershell script that installed them from there.
Thanks a lot to those that commented on the issue.
It appears that nothing is being passed from the MSBuild target file to the powershell script. You are defining the name of the service involved via ' marks, so its taking that rather than what the MSBuild target file is doing.
I would suggest you find out how to pass this variable, otherwise the script won't be able to pick this up correctly.
I'm making a complete stab in the dark, but your service name needs the TopShelf instance name including when you call get-service.
For example your service might be called "MyWindowsService" but what you need to programmatically look for is "MyWindowsService$default"
I am trying to run queries stored in a text file from PowerShell. I use following to do that;
Invoke-Expression 'sqlcmd -d TestDB -U $user -P $pw -i "E:\SQLQuery1.sql"'
If an error or exception occurs when executing the queries from the .sql file, how can I capture that in my Powershell script? How can I get the script output?
NOTE: I cannot use invoke-sqlcmd
To answer the question
If some error or exception occurred when executing .sql file how can I get that into my PowerShell script? How can I get the script output?"
Invoke-Expression returns the output of the expression executed. However, it may only capture STDOUT, not STDERR (I haven't tested, as I don't use this method), so you might not get the actual error message.
From the Help:
The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command
The better route is to use the PowerShell method you already have available - Invoke-SQLCmd is installed if you have installed any of the SQL Server 2008 (or newer) components/tools (like SSMS). If you've got SQL Server 2012, it's very easy: import-module sqlps. For 2008, you need to add a Snap-In, add-pssnapin SqlServerCmdletSnapin. And since you have sqlcmd.exe, the PowerShell components should be there already.
If all else fails, go the System.Data.SQLClient route:
$Conn=New-Object System.Data.SQLClient.SQLConnection "Server=YOURSERVER;Database=TestDB;User Id=$user;password=$pw";
$Conn.Open();
$DataCmd = New-Object System.Data.SqlClient.SqlCommand;
$MyQuery = get-content "e:\SQLQuery1.sql";
$DataCmd.CommandText = $MyQuery;
$DataCmd.Connection = $Conn;
$DAadapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$DAadapter.SelectCommand = $DataCmd;
$DTable = New-Object System.Data.DataTable;
$DAadapter.Fill($DTable)|Out-Null;
$Conn.Close();
$Conn.Dispose();
$DTable;
With both this and Invoke-SQLCmd, you'll be able to use try/catch to pick up and handle any error that occurs.
As seen in this question's answers, there is a method built into Powershell to invoke SQLCMD called, unsurprisingly, Invoke-Sqlcmd.
It's very easy to use for individual files:
Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $filename
Or groups:
$listOfFiles | % { Invoke-sqlcmd -ServerInstance $server -Database $db -InputFile $_ }
Use invoke-sqlquery module, available at this website.