I have been using SublimeText for a while now and haven't had any issues until recently. Every now and again if I write a PowerShell script in sublime text, the script will fail to run with generic errors such as:
Missing closing '}' in statement block or type definition.
At C:\Users\user\OneDrive\Scripts\~Projects\TRIS-AzureMigration\TRIS-AzureMigration.ps1:7 char:25
+ function Get-SQLInstall {
These errors only show if I call the script from PowerShell. If I copy the script to the console it works. If I run it in ISE it works. I can also run it if I copy the code to notepad and then save it as a .ps1 from there, this tells me that it is something to do with sublime text.
I have had a look around and found a few examples:
My script runs in powershell ISE but not powershell console? :: solution was to copy to a new file, which i dont want to have to do every time.
Any help on this would be much appreciated.
The code block of code that for the error above:
function Get-SQLInstall {
param ( [parameter(Mandatory=$false)]$GetMax = $true)
$instances = #();
$inst = (get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server').InstalledInstances;
foreach ($i in $inst)
{
$p = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL').$i;
[version]$v = ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").Edition).Major;
$instances += New-Object –TypeName PSObject -Property #{
Edition = ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").Edition);
version = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").Version;
SQLPath = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").SqlPath;
SQLFullPath = ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$p\Setup").SqlPath) + "\" + [string]$v.major + "0";
}
}
if ($GetMax -eq $true) {
## Get latest SQL version
$max = ($instances | measure-object -Property version -maximum).maximum;
return #($instances | ? { $_.version -eq $max})[0];
}
else {
return $instances;
}
}
Related
$global:af_fp = "C:\Path\to\folder\"
Function function-name {
do things …
$global:af_fp = $global:af_fp + $variableFromDo_things + "_AF.csv"
}
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
Above is the generalized (and abbreviated) script contents for a powershell script.
Every time I run the script in this way, I get the following error:
Add-Content : Could not find a part of the path 'C:\Users\timeuser\Documents\'.
At C:\Users\timeuser\Documents\get_software.ps1:231 char:51
+ ... ware | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\timeuser\Documents\:String) [Add-Content], DirectoryNotFoundException
+ FullyQualifiedErrorId : GetContentWriterDirectoryNotFoundError,Microsoft.PowerShell.Commands.AddContentCommand
When I run
Get-Variable -Scope global
after running the script and seeing the error, the variable af_fp contains exactly the information I am seeking for the file name, however, the error shows the variable contents ending in ':String'.
To confuse me even more, if I comment out the lines containing '$global:...' and re-run the same script, IT ACTUALL RUNS AND SAVES THE FILE USING THE LINE
function-name | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
AS INTENDED. Of course, I had to run the script and watch it error first, then re-run the script with the global variable declaration and update commented out for it to actually work. I want to run the script ONCE and still get the same results.
FYI, I am a complete noob to powershell, but very familiar with the concept of variable scope.....but why is this global not working when initially created and updated, but then work the second time around, when, as far as I can tell, the CONTENT AND SCOPE of the global remains the same...…. any assistance to finding a solution to this small issue would be greatly appreciated; I have tried sooooo may different methods from inquiries through here and on Google...…..
EDIT: not sure why this will matter, because the script ran before as intended when I explicitly typed the parameter for -Path as 'C:\path\to\file'. The ONLY CHANGES MADE to the original, working script (below) were my inclusion of the global variable declaration, the update to the contents of the global variable (near the end of the function), and the attempt to use the global variable as the parameter to -Path, that is why I omitted the script:
'''
$global:af_fp = "C:\Users\timeuser\Documents\"
Function Get-Software {
[OutputType('System.Software.Inventory')]
[Cmdletbinding()]
Param(
[Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[String[]]$Computername = $env:COMPUTERNAME
)
Begin {
}
Process {
ForEach ($Computer in $Computername) {
If (Test-Connection -ComputerName $Computer -Count 1 -Quiet) {
$Paths = #("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "SOFTWARE\\Wow6432node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
ForEach ($Path in $Paths) {
Write-Verbose "Checking Path: $Path"
# Create an instance of the Registry Object and open the HKLM base key
Try {
$reg = [microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine', $Computer, 'Registry64')
}
Catch {
Write-Error $_
Continue
}
# Drill down into the Uninstall key using the OpenSubKey Method
Try {
$regkey = $reg.OpenSubKey($Path)
# Retrieve an array of string that contain all the subkey names
$subkeys = $regkey.GetSubKeyNames()
# Open each Subkey and use GetValue Method to return the required values for each
ForEach ($key in $subkeys) {
Write-Verbose "Key: $Key"
$thisKey = $Path + "\\" + $key
Try {
$thisSubKey = $reg.OpenSubKey($thisKey)
# Prevent Objects with empty DisplayName
$DisplayName = $thisSubKey.getValue("DisplayName")
If ($DisplayName -AND $DisplayName -notmatch '^Update for|rollup|^Security Update|^Service Pack|^HotFix') {
$Date = $thisSubKey.GetValue('InstallDate')
If ($Date) {
Try {
$Date = [datetime]::ParseExact($Date, 'yyyyMMdd', $Null)
}
Catch {
Write-Warning "$($Computer): $_ <$($Date)>"
$Date = $Null
}
}
# Create New Object with empty Properties
$Publisher = Try {
$thisSubKey.GetValue('Publisher').Trim()
}
Catch {
$thisSubKey.GetValue('Publisher')
}
$Version = Try {
#Some weirdness with trailing [char]0 on some strings
$thisSubKey.GetValue('DisplayVersion').TrimEnd(([char[]](32, 0)))
}
Catch {
$thisSubKey.GetValue('DisplayVersion')
}
$UninstallString = Try {
$thisSubKey.GetValue('UninstallString').Trim()
}
Catch {
$thisSubKey.GetValue('UninstallString')
}
$InstallLocation = Try {
$thisSubKey.GetValue('InstallLocation').Trim()
}
Catch {
$thisSubKey.GetValue('InstallLocation')
}
$InstallSource = Try {
$thisSubKey.GetValue('InstallSource').Trim()
}
Catch {
$thisSubKey.GetValue('InstallSource')
}
$HelpLink = Try {
$thisSubKey.GetValue('HelpLink').Trim()
}
Catch {
$thisSubKey.GetValue('HelpLink')
}
$Object = [pscustomobject]#{
#Potential Candidate for AssetID in the TIME system
AssetID = $Computer
#String that contains word or word combinations for the product field of CPE WFN; may also contain the valid values necessary for update, edition, language, sw_edition, target_hw/sw fields as well.
cpeprodinfo = $DisplayName
cpeversion = $Version
InstallDate = $Date
cpevendor = $Publisher
UninstallString = $UninstallString
InstallLocation = $InstallLocation
InstallSource = $InstallSource
HelpLink = $thisSubKey.GetValue('HelpLink')
EstimatedSizeMB = [decimal]([math]::Round(($thisSubKey.GetValue('EstimatedSize') * 1024) / 1MB, 2))
}
$Object.pstypenames.insert(0, 'System.Software.Inventory')
Write-Output $Object
}
}
Catch {
Write-Warning "$Key : $_"
}
}
}
Catch { }
$reg.Close()
}
}
Else {
Write-Error "$($Computer): unable to reach remote system!"
}
$global:af_fp = $global:af_fp + $Computer + "_AF.csv"
}
}
}
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $($af_fp)
'''
IGNORE FORMATTING PLEASE- HAD TROUBLE MAKING INDENTS CORRECTLY FROM COPY-PASTE AND RESTRICTIONS ON SITE FOR CODE BLOCKS.....
NOTE: the ONLY changes I made, that I am asking about, are the global declaration, the global variable update in the function, and the attempt to use the global variable for the -Path parameter....script otherwise runs and will even run WITH THE LAST LINE AS IS if I ran it and errored the first time.....not sure how the addition script will help in any way, shape, or form!
With a little effort, Nasir's solution worked! HOWEVER, I ran across a sample file that had a way of adding to a parameter that inspired me to make a change to my ORIGINAL, that also worked: remove global variable from script entirely and add this code the very end:
$file_suffix = '_AF.csv'
Get-Software | ConvertTo-CSV -NoTypeInformation | Add-Content -Path $env:COMPUTERNAME$file_suffix
In this way, I was able to accomplish exactly what I was setting out to do! Thanks Nasir for your response as well! I was able to also make that work as intended!
Global variables are generally frowned upon, since they often lead to poor scripts, with hard to debug issues.
It seems like your function returns some stuff, which you need to write to a file, the name of which is also generated by the same function. You can try something like this:
function function-name {
param($PathPrefix)
#do things
[pscustomobject]#{"DoThings_data" = $somevariablefromDoThings; "Filename" = "$($PathPrefix)$($variableFromDo_Things)_AF.csv"}
}
function-name -PathPrefix "C:\Path\to\folder\" | Foreach-Object { $_.DoThings_data | Export-Csv -Path $_.Filename -NoTypeInformation }
Or just have your function write the CSV data out and then return the data if you need to further process it outside the function.
Edit: this is just me extrapolating from partial code you have provided. To Lee_Dailey's point, yes, please provide more details.
I have slightly modified this PowerShell script for my work with DOCX files but would like to edit the DOCX file in place.
After running the script, the terminal displays the error message: "You cannot call a method on a null-value expression" in reference to line 43, $document.Save().
I am not sure the reason for the error as the path to $document is already defined earlier in the script. What am I missing?
Here is the whole script:
Param ([string]$path = $(throw "-path is required."))
Import-Module "C:\scripts\PSGenericMethods.psm1"
[System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Open XML SDK\V2.5\lib\DocumentFormat.OpenXml.dll") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Packaging") | out-null
[Reflection.Assembly]::LoadWithPartialName("DocumentFormat.OpenXml.Wordprocessing") | out-null
[Reflection.Assembly]::LoadWithPartialName("OpenXmlPowerTools") | out-null
[DocumentFormat.OpenXml.Packaging.WordprocessingDocument]$document = $null
$document = [DocumentFormat.OpenXml.Packaging.WordprocessingDocument]::Open($path, $true)
[DocumentFormat.OpenXml.Packaging.MainDocumentPart]$MainDocumentPart = $document.MainDocumentPart
[DocumentFormat.OpenXml.Wordprocessing.Document]$InnerDocument = $document.Document
[DocumentFormat.OpenXml.Wordprocessing.Body]$Body = $document.Body
[DocumentFormat.OpenXml.Wordprocessing.Paragraph]$paragraph = $document.Paragraph
[DocumentFormat.OpenXml.Wordprocessing.ParagraphMarkRunProperties]$ParagraphMarkRunProperties = $document.ParagraphMarkRunProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphProperties]$ParagraphProperties = $document.ParagraphProperties
[DocumentFormat.OpenXml.Wordprocessing.ParagraphStyleId]$ParagraphStyleId = $document.ParagraphStyleId
[DocumentFormat.OpenXml.Wordprocessing.Run]$run = $document.Run
$paragraphs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Paragraph
$runs = Invoke-GenericMethod -InputObject $MainDocumentPart.Document -MethodName Descendants -GenericType DocumentFormat.OpenXml.Wordprocessing.Run
foreach ($run in $runs) {
if ($run.RunProperties.Languages.Val) {
<#[String]$value = $run.InnerText#>
[String]$language = $run.RunProperties.Languages.Val
'{{$span xml:lang="{0}"$}}{1}{{$/span}}$' -f $language, $run.InnerText
}
}
$document.close()
Update:
After modifying the script, I now no longer have the problem with "You cannot call a method on a null value expression" error. The problem is that I am not sure how to save results to the file.
This may be a duplicate of this thread.
Can anyone suggest a method for saving the changes to the file?
Variable names in PowerShell are NOT case-sensitive. You destroy your "document" with this line I think:
[DocumentFormat.OpenXml.Wordprocessing.Document]$Document = $document.Document
Because $Document is the same variable as $document.
Trying to seemingly generate text in Powershell console (result predetermined), did not like cls + Write-Host because of constant blinking, tried to use PSConsoleReadLine. Code below works in some situations, while it does not in others.
Code works when:
directly copy pasted into Powershell window
when stored in a script file and the file is called from Powershell window
Code does not work when:
launched in ISE (known issue)
right click on the script file / run with powershell
Tried to get around the error with Add-Type, but it ends with IOException and null reference instead. Is present in code below, but commented.
Tried to write a script in a way that relaunches itself in a new Powershell process to handle it, did not help.
Code itself (probably could reproduce the issue with a single line of code, but the guide says one line of a code is not enough):
function tryOne($oldExpression, $targetExpression, $currentPosition, $src)
{
While($targetExpression[$currentPosition] -cne $triedCharacter)
{
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()#here problem
$triedCharacter = Get-Random -InputObject $src
$expression = $oldExpression + $triedCharacter
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($expression)#here problem
Start-sleep -Milliseconds 50
}
Return $expression
}
cd $PSScriptRoot
#Add-Type -path "C:\Program Files\WindowsPowerShell\Modules\PSReadline\1.2\Microsoft.PowerShell.PSReadLine.dll"
#above helps with not found error, with it uncommented the errors at RevertLine and Insert are IOException and null reference
$targetExpression = "Not ideal"
$oldExpression = ""
$currentPosition = 0
#region AllowedCharacters
$src = New-Object System.Collections.ArrayList
for($i=65;$i-le 90;$i++)
{
$src.Add([char]$i) | Out-Null
}
for($i=97;$i-le 122;$i++)
{
$src.Add([char]$i) | Out-Null
}
for($i=0;$i-le 9;$i++)
{
$src.Add($i) | Out-Null
}
$src.Add(" ") | Out-Null #allowed characters in the target sentence - A-Z, a-z, 0-9, space
#endregion
While($currentPosition -lt ($targetExpression.length))
{
$oldExpression = tryOne $oldExpression $targetExpression $currentPosition $src
$currentPosition++;
}
I'd like to be able to launch the script via right click / run with Powershell.
I'm in the process of automating my .Net solution build to be completely in PowerShell. I want to locate MSTest.exe using PowerShell.
I used the following script to locate MSBuild.exe and I hope that I can have something similar to locate MSTest.exe
$msBuildQueryResult = reg.exe query "HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0" /v MSBuildToolsPath
$msBuildQueryResult = $msBuildQueryResult[2]
$msBuildQueryResult = $msBuildQueryResult.Split(" ")
$msBuildLocation = $msBuildQueryResult[12] + "MSBuild.exe"
Any directions ?
The following works with Visual Studio 2010 and higher[1]:
# Get the tools folder location:
# Option A: Target the *highest version installed*:
$vsToolsDir = (
Get-Item env:VS*COMNTOOLS | Sort-Object {[int]($_.Name -replace '[^\d]')}
)[-1].Value
# Option B: Target a *specific version*; e.g., Visual Studio 2010,
# internally known as version 10.0.
# (See https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#History)
$vsToolsDir = $env:VS100COMNTOOLS
# Now locate msbuild.exe in the "IDE" sibling folder.
$msTestExe = Convert-Path -EA Stop (Join-Path $vsToolsDir '..\IDE\MSTest.exe')
The approach is based on this answer and is generalized and adapted to PowerShell.
It is based on system environment variables VS*COMNTOOLS, created by Visual Studio setup, where * represents the VS version number (e.g., 100 for VS 2010).
Re option A: Sort-Object is used to ensure that the most recent Visual Studio installation is targeted, should multiple ones be installed side by side:
The script block used for sorting first extracts only the embedded version number from the variable name ($_.Name -replace '[^\d]'; e.g., 100 from VS100COMNTOOLS) and converts the result to an integer ([int]); [-1] then extracts the last element from the sorted array - i.e., the variable object whose names has the highest embedded version number - and accesses its value (.Value).
The IDE subfolder, in which MSTest.exe is located is a sibling folder of the tools folder that VS*COMNTOOLS points to.
If MSTest.exe is NOT in the expected location, Convert-Path will throw a non-terminating error by default; adding -EA Stop (short for: -ErrorAction Stop) ensures that the script is aborted instead.
[1]
- I've tried up to Visual Studio 2015; do let me know whether or not it works on higher versions.
- Potentially also works with VS 2008.
Perhaps you are wanting something like this?
$regPath = "HKLM:\SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0"
$regValueName = "MSBuildToolsPath"
$msBuildFilename = "MSBUild.exe"
if ( Test-Path $regPath ) {
$toolsPath = (Get-ItemProperty $regPath).$regValueName
if ( $toolsPath ) {
$msBuild = Join-Path $toolsPath $msBuildFilename
if ( -not (Test-Path $msBuild -PathType Leaf) ) {
Write-Error "File not found - '$msBuild'"
}
}
}
# Full path and filename of MSBuild.exe in $msBuild variable
My way of getting mstest path.
GetMSTestPath function is main function which you call and then if first GetMsTestPathFromVswhere function will find something it returns path if not your will be making a long search for mstest.exe. Usually, it takes approximately 10 sec. I know that this is not the best but at least it is something when you struggle to find mstest.exe. Hope it will be helpful for somebody. :)))
function GetMSTestPath
{
function GetTime()
{
$time_now = Get-Date -format "HH:mm:ss"
return $time_now;
}
function GetMsTestPathFromVswhere {
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$path = & $vswhere -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath
#write-host $path
if ($path) {
$tool = join-path $path 'Common7\IDE\MSTest.exe'
if (test-path $tool) {
return $tool
}
return ""
}
}
function SeachForMsTestPath
{
write-host $(GetTime)
$path = Get-ChildItem C:\ -Filter MSTest.exe -Recurse -ErrorAction Ignore | ? { $_.VersionInfo.FileDescription -eq 'Test Execution Command Line Tool' } | Select -First 1
write-host $(GetTime)
return $path
}
$msTestExePath = GetMsTestPathFromVswhere
if ([string]::IsNullOrEmpty($msTestExePath))
{
$msTestExePath = SeachForMsTestPath;
if ([string]::IsNullOrEmpty($msTestExePath))
{
Write-host "MsTest path is not found. Exiting with error"
Exit -1
}
}
return $msTestExePath;
}
Thanks #Bill_Stewart , I used your comments to write this working function:
function Get-MSTest-Location {
$msTests = #()
$searchResults = Get-ChildItem C:\* -Filter MSTest.exe -Recurse -ErrorAction Ignore
foreach($searchResult in $searchResults) {
try{
if(($searchResult.VersionInfo -ne $null) -and ($searchResult.VersionInfo.FileDescription -eq "Test Execution Command Line Tool"))
{ $msTests = $msTests + $searchResult.FullName }
}
catch{}
}
if($msTests.Length -eq 0)
{return "MSTest not found."}
return $msTests[0]
}
I am creating a large batch script to check installed windows features (components) on a Windows 2003 server. I can not seem to figure out how to query server roles and display all sub-features of the role within the cmd shell. This is easy to do in Windows Server 2008 by simply using servermanager.exe or WMI, but I cannot figure out what program or cmd to use in Windows 2003. Windows Server 2003 has power shell installed, but it just seems like a glorified cmd shell in this Windows OS version. Does anyone know of a similar utility or cmd that can be used specifically on a Windows 2003 box? Thanks for your time.
You can try this function
function Get-InstalledComponents($computer = '.') {
$components_installed = #();
$reg_paths = #('SOFTWARE\Microsoft\Windows\CurrentVersion'`
+ '\Setup\Oc Manager\Subcomponents');
$reg_paths += #('SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion'`
+ '\Setup\Oc Manager\Subcomponents');
$hkey = 'LocalMachine';
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hkey, $computer);
foreach ($reg_path in $reg_paths) {
$reg_key = $reg.OpenSubKey($reg_path);
if ($reg_key -eq $null) {
continue;
}
$names = $reg_key.GetValueNames();
foreach ($name in $names) {
$value = $reg_key.GetValue($name);
if ($value -gt 0) {
$components_installed += #($name);
}
}
$reg_key.close();
}
$reg.close();
if ($components_installed.count -lt 1) {
trap { ;
continue } $features = #(get-wmiobject -class 'Win32_ServerFeature' `
-computer $computer -erroraction 'Stop');
foreach ($feature in $features) {
$components_installed += #($feature.name);
}
}
return ($components_installed | sort);
}