воскресенье, 27 января 2013 г.

Настройка WLAN в Debian Squeeze 6.0.6 stable для HAMA WLAN USB-Stick

[Setting up WLAN in Debian Squeeze 6.0.6 stable for HAMA WLAN USB-Stick]

После установки дистрибутива Debian-Stable 6.0.6 Squeeze не работал WLAN.
На борту находился WLAN-USB-Stick HAMA 62734.

Для его поднятия опирался на это руководство. Итак, по пунктам:

- Установил два пакета для работы с WLAN:
"apt-get install wireless-tools wpasupplicant"
- Команда "lspci" устройства, как-то обозначающиего WLAN, не находила, но нагуглил что мой стик базируется на чип-сете RT73USB.
- В "/etc/apt/sources.list" добавил строки:
deb http://ftp.de.debian.org/debian squeeze contrib non-free
deb-src http://ftp.de.debian.org/debian squeeze contrib non-free
Хотя, конечно же, можно было бы добавить "contrib non-free" к уже существующим источникам с флагом "main"
- Обновил список пакетов командой:
"aptitude update"
- Через Synaptic Менеджер Пакетов нашел необходимый для этого устройства пакет драйверов: "firmware-ralink". Здесь же его и установил.
- Перегрузил сеть командой: "/etc/init.d/networking restart"
- Проверил, что драйвера загружены командой "lsmod | grep ке73", которая выдает:
rt73usb 17605 0
crc_itu_t 1035 1 rt73usb
rt2x00usb 5673 2 rt73usb,rt2500usb
rt2x00lib 19101 3 rt73usb,rt2500usb,rt2x00usb
usbcore 99426 7 rt73usb,rt2500usb,rt2x00usb,usbhid,uhci_hcd,ehci_hcd
- Команда "iwconfig" должна отобразить для устройства wlan0 строку начинающуюся с "IEEE ..."
- Устройство готово к работе.


Сетевые настройки предпочитаю делать с помощью NetworkManager. Если он еще не установлен, можно это сделать следующей командой: "apt-get install network-manager-gnome". Но менеджер у меня не захотел работать из коробки, и беспроводные соединения оставались неактивными и в нем отображалось что-то вроде: "Не могу конфигурировать устройство". Для разрешения проблемы опираясь на это сделал примерно следующее:

- В файле '/etc/network/interfaces' закомментировал все упоминания eth и wlan. Устройство 'lo' не трогал
- В файле '/etc/NetworkManager/NetworkManager.conf' поменял строчку "managed=false" на "managed=true".
- Перегрузил менеджер соединений командой:
"/etc/init.d/network-manager restart"
- В менеджере выбрал точку WLAN и настроил соединение.

воскресенье, 25 ноября 2012 г.

Хранение однотипных групп пользовательских настроек, созданных во время выполнения приложения [Visual Studio, .NET]

[Using of the user scope settings for the storing the similar setting groups in run-time]

Платформа .NET и Visual Studio предлагает относительно развитые возможности для простого использования настроек приложения. Это, например, разделение настроек на пользовательские (UserScope) и программные (AppScope), их статическая типизация, наличие встроенного в Visual Studio дизайнера и прочие вкусности. Неплохой обзор для VS 2005, но все еще актуальный, можно посмотреть здесь:
http://msdn.microsoft.com/en-us/library/aa730869(v=vs.80).aspx

Всего этого достаточно, чтобы покрыть большинство потребностей небольшого приложения. Но если вам когда-нибудь понадобиться выйти за границы статического объявления настроек и/или захочется хранить данные в своем формате и тем не менее оставаться в рамках стандартных механизмов .NET, вам, скорее всего, придется разбираться с классами ConfigurationManager и ConfigurationSection:
http://msdn.microsoft.com/en-us/library/system.configuration.configurationmanager(v=vs.100).aspx

Так случилось и со мной, мне понадобилось хранить группы определенных настроек, количество которых определялось во время выполнения программы. Группы нужно было идентифицировать по некоторому ключу, в моем случае им была уникальная строка. Но в своем решении я избежал сложностей испльзования ConfigurationManager, а просто “вложил” свою группу настроек в одну пользовательскую установку. Решение получилось достаточно простым, но не совсем очевидным, поэтому с удовольствием спешу поделиться этим знанием :)

Итак, дизайнер настроек позволяет определять различные предопределенные типы настроек. Последним пунктом в этом списке можно выбрать пользовательский тип из некоторого количества предоставленных имен пространств. Список этот ограничен, но можно вписать полный путь к произвольному типу вручную.


Здесь-то мы и добавим наш “контейнер” групп, но для начала нужно его объявить.

Для многократного использования общей конструкции я использовал общий (generic) класс. В моей задаче мне нужно было ко всему прочему иметь “глобальное” значение, которое бы использовалось как стандартное для всех новых и/или неопределенных значений. Ниже код:

public class SettingSetBase<T>
{
   public SettingSetBase() { // ctor
     KeyValList = new List<KeyVal<T>>();
     DefaultValue = default(T);
   }

   public SettingSetBase(T defVal) { // ctor
     KeyValList = new List<KeyVal<T>>();
     DefaultValue = defVal;
   }

   public T DefaultValue { get; set; }
   public List<KeyVal<T>> KeyValList { get; set; }

   public T GetValue(string key) {
     KeyVal<T> keyVal = KeyValList.FirstOrDefault(x => String.Equals(x.Key, key, StringComparison.CurrentCultureIgnoreCase));
     if (keyVal == null) return DefaultValue;
     else return keyVal.Val;
   }

   public void SetValue(String key, T val) {
     KeyVal<T> keyVal = KeyValList.FirstOrDefault(x => String.Equals(x.Key, key, StringComparison.CurrentCultureIgnoreCase));
     if (keyVal == null)
       KeyValList.Add(new KeyVal<T>() { Key = key, Val = val });
     else
       keyVal.Val = val;
   }
}

public class KeyVal<T>
{
   public String Key { get; set; }
   public T Val { get; set; }
}


* This source code was highlighted with Source Code Highlighter.

Не каждый тип может быть использован в качестве типа установки, важное условие - он должен быть Xml-сериализуемым. Избегая излишних сложностей сериализации словарей (Dictionary) применил List и пары значение ключ.

Теперь этот generic-класс можно “конкретизировать” любым сериализуемым типом:

// Например для String:
public class SettingSetString : SettingSetBase<String>
{
   public SettingSetString() { }
   public SettingSetString(String defVal) : base(defVal) { }
}

// Или для булевых значений:
public class SettingSetBool : SettingSetBase<bool>
{
   public SettingSetBool() { }
   public SettingSetBool(bool defVal) : base(defVal) { }
}

// Или для глобального уникального идентификатора (GUID):
public class SettingSetGuid : SettingSetBase<Guid>
{
   public SettingSetGuid() { }
   public SettingSetGuid(Guid defVal) : base(defVal) { }
}

// Или даже для его обнулямого варианта:
public class SettingSetGuidNullable : SettingSetBase<Guid?>
{
   public SettingSetGuidNullable() { }
   public SettingSetGuidNullable(Guid? defVal) : base(defVal) { }
}


* This source code was highlighted with Source Code Highlighter.

Конечно же, элементарными типами дело не ограничивается и можно использовать комплексные xml-сериализуемые типы, как например этот для хранения параметров окон:
public class WinPosSett
{
  public Point Location { get; set; }
  public int Width { get; set; }
  public int Height { get; set; }
}


* This source code was highlighted with Source Code Highlighter.

Теперь все готово, можно объявить контейнеры настроек в дизайнере. Ограничусь для демонстрации двумя видами - SettingSetString и SettingSetWinPosSett:

Замечание! Что бы эти типы стали доступными для выбора, необходимо пересоздать проект.

Как вы может быть уже заметили, для таких типов в дизайнере невозможно определить стандартное значение (колонка “Value”, или “Wert“ у меня в немецкой студии). Поэтому просто, не усложняя, расширим созданный генератором класс настроек следующим методом:

internal sealed partial class Settings
{
   public void InitDefaults() {
     if (MyStringSet == null)
       MyStringSet = new SettingSetString("DefaultValue");
     if (MyWinPosSet == null)
       MyWinPosSet = new SettingSetWinPosSett(new WinPosSett() { Height = 300, Width = 300, Location = new Point(100, 100) });
   }
}


* This source code was highlighted with Source Code Highlighter.

Ну все, можно пользоваться:
Properties.Settings.Default.InitDefaults();
String strGroup1 = Properties.Settings.Default.MyStringSet.GetValue("Group1"); // "DefaultValue"
Properties.Settings.Default.MyStringSet.SetValue("Group1", "My new Value");
Properties.Settings.Default.Save();
String strGroup1NewValue = Properties.Settings.Default.MyStringSet.GetValue("Group1"); // "My new Value"
String strGroup2 = Properties.Settings.Default.MyStringSet.GetValue("Group2"); // "DefaultValue"


* This source code was highlighted with Source Code Highlighter.

Вот как выглядит часть файла user.config после выполненных операций:

<setting name="MyStringSet" serializeAs="Xml">
  <value>
    <SettingSetString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <DefaultValue>DefaultValue</DefaultValue>
      <KeyValList>
        <KeyValOfString>
          <Key>Group1</Key>
          <Val>My new Value</Val>
        </KeyValOfString>
      </KeyValList>
    </SettingSetString>
  </value>
</setting>


* This source code was highlighted with Source Code Highlighter.

Неплохо! Теперь попробуем использовать комплексный тип WinPosSett:
WinPosSett WinPosMainWindow = Properties.Settings.Default.MyWinPosSet.GetValue("MainForm");
// .. используем значения для установки размеров и позиции окна
// ..
// .. окно закрывается, сохраняем значения:
Properties.Settings.Default.MyWinPosSet.SetValue("MainForm", new WinPosSett() { Height = 400, Width = 200, Location = new Point(10, 10) });
Properties.Settings.Default.Save();


* This source code was highlighted with Source Code Highlighter.

Сохраненные настройки выглядят так:
<setting name="MyWinPosSet" serializeAs="Xml">
<value>
   <SettingSetWinPosSett xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema">
     <DefaultValue>
       <Location>
         <X>100</X>
         <Y>100</Y>
       </Location>
       <Width>300</Width>
       <Height>300</Height>
     </DefaultValue>
     <KeyValList>
       <KeyValOfWinPosSett>
         <Key>MainForm</Key>
         <Val>
           <Location>
             <X>10</X>
             <Y>10</Y>
           </Location>
           <Width>200</Width>
           <Height>400</Height>
         </Val>
       </KeyValOfWinPosSett>
     </KeyValList>
   </SettingSetWinPosSett>
</value>
</setting>


* This source code was highlighted with Source Code Highlighter.

Пример приложения можно скачать здесь:
https://sites.google.com/site/selosite/GroupUserSettings.zip

воскресенье, 30 сентября 2012 г.

Добавление авто-подписи к каждому сообщению на blogspot

В одном своем блоге, который я использую как платформу для опубликования снимков различных объектов (своеобразный клипарт) мне захотелось добавить авто-подпись для всех опубликованных постов. В этой подписи я хотел еще раз упомянуть о том, что фотографии свободны и бесплатны для использования.

В blogspot можно создать шаблон, который используется при создании новых сообщений. Но если вы когда-нибудь захотите изменить авто-подпись, придется править ВСЕ сообщения вручную. Других стандартных способов для решения этой проблемы я не нашел и .. изобрел свой :)

Для этого было решено воспользоваться JavaScript для динамического изменения DOM (Document Object Model) страницы. JavaScript можно добавить на блог как гаджет в его дизайнере:



Подсмотрев код HTML блога установил, что все посты блога заключены в элемент DIV с классом "post-body entry-content". Этого абсолютно достаточно знать, что бы как я, не зная JavaScript (лишь смутные представления) при помощи поисковика накодить следующий скрипт:

<script type='text/javascript'>
  // Тест, что DIV элементы скриптом находятся:
  // var posts = document.getElementsByClassName("post-body entry-content");
  // document.write("Posts: " + posts.length);

  var posts = document.getElementsByClassName("post-body entry-content");
  for (var i=0; i<posts.length; i++) {
    newDiv = document.createElement("div");

    // a) Содержимое DIV можно наполнить так:
    // newLine1 = document.createTextNode("Этот текстовый элемент создан при помощи createTextNode()");
    // newDiv.appendChild(newLine1);

    // b) но мы воспользуемся другим способом: newDiv.innerHTML = '<li>text</li>';
    str = "<span style='font-size: smaller;'>Это первая строка HTML-кода.</span><BR/><span style='font-size: smaller;'>Вторая строка.</span>";
    newDiv.innerHTML = str;

    // Добавим созданный DIV после DIV-сообщения.
    // К сожалению, не существует insertAfter-функции, поэтому эмулируем ее с помощью "parentElement" и "nextSibling":
    posts[i].parentElement.insertBefore(newDiv, posts[i].nextSibling);
  }
</script>


* This source code was highlighted with Source Code Highlighter.


Название в окне настроек гаджета я оставил пустым - теперь о наличии скрипта можно только догадываться.


Результат вы можете посмотреть на моем клипарт-блоге

Ссылки по теме:
Document Object Model (http://en.wikipedia.org/wiki/Document_Object_Model)
document.getElementsByClassName (https://developer.mozilla.org/en-US/docs/DOM/document.getElementsByClassName)
Adding elements to the DOM (http://www.javascriptkit.com/javatutors/dom2.shtml)

понедельник, 24 сентября 2012 г.

Автоматическое изменение размеров FlowLayoutPanel в XtraLayoutControl (DevExpress)


[FlowLayoutPanel with autosize in XtraLayoutControl]

Работая с библиотекой элементов управления для WinForms от DevExpress в ходе практики накопилась небольшая тележка различных решений, обходных путей и просто интересных подходов. Со временем планирую их здесь опубликовать. Ну а пока, покажу, как я реализовал автоматическое изменение размеров FlowLayoutPanel в XtraLayoutControl, так чтобы все элементы, помещенные в FlowLayoutPanel, были всегда видны.

На рисунках отображен уже конечный результат, которого я хотел добиться:






XtraLayoutControl отлично управляет размерами и расположениями “своих” элементов управления из библиотеки DevExpress, но совершено не справляется со стандартными элементами управления, к которым относится FlowLayoutPanel. Дело в том, что XtraLayoutControl взаимодействует с элементами своеобразным образом и требует от элементов реализации специфичных интерфейсов. В нашем случае достаточно использовать интерфейс IXtraResizableControl, который я реализовал в унаследованном от панели классе:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DevExpress.XtraLayout;
using System.Drawing;
using DevExpress.Utils.Controls;

namespace WindowsFormsApplication9
{
    public class MyFlowLayoutPanel : FlowLayoutPanel, IXtraResizableControl
    {
        public MyFlowLayoutPanel() { // ctor
            
        }

        LayoutControl rootLayoutControl = null;
        LayoutControlItem parentControlItem = null;

        protected override void OnHandleCreated(EventArgs e) {
            base.OnHandleCreated(e);

            if (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime)
                return;

            LayoutControl parent = this.Parent as LayoutControl;
            if (parent != null) {

                this.rootLayoutControl = parent;
                this.parentControlItem = FindItemByContentControl(parent.Root, this);

                CalcAndUpdateSize();
                this.rootLayoutControl.Resize += (x, y) => { CalcAndUpdateSize(); };
                this.ControlAdded += (x, y) => { CalcAndUpdateSize(); };
                this.ControlRemoved += (x, y) => { CalcAndUpdateSize(); };
            }
        }

        void layoutControl_Changing(object sender, EventArgs e) {
            CalcAndUpdateSize();
        }

        // REM. http://www.devexpress.com/Support/Center/p/Q396747.aspx (Hot to find a conatner LayoutcontrolItem of a control)
        LayoutControlItem FindItemByContentControl(LayoutControlGroup layoutGroup, Control contentControl) {

            foreach (BaseLayoutItem baseLI in layoutGroup.Items) {
                LayoutControlItem lci = baseLI as LayoutControlItem;
                if (lci != null && lci.Control == contentControl)
                    return lci;

                LayoutControlGroup lcg = baseLI as LayoutControlGroup;
                if (lcg != null)
                    return FindItemByContentControl(lcg, contentControl);
            }
                
            return null;
        }


        private void CalcAndUpdateSize() {
            if (parentControlItem == null || parentControlItem.ViewInfo == null) return;

            this.MaximumSize = new Size(parentControlItem.ViewInfo.ClientArea.Width, 0);
            RaiseSizeChanged();
        }

        #region IXtraResizableControl Member

        event EventHandler ChangedCore;
        event EventHandler IXtraResizableControl.Changed {
            add { ChangedCore += value; }
            remove { ChangedCore -= value; }
        }

        protected void RaiseSizeChanged() {
            if (ChangedCore != null) ChangedCore(this, EventArgs.Empty);
        }

        bool IXtraResizableControl.IsCaptionVisible { get { return false; } }

        Size IXtraResizableControl.MaxSize { get { return new Size(0, this.PreferredSize.Height); } }

        Size IXtraResizableControl.MinSize { get { return new Size(0, this.PreferredSize.Height); } }

        #endregion
    }
}

Ссылки по теме:

IXtraResizableControl Interface:
http://documentation.devexpress.com/#WindowsForms/clsDevExpressUtilsControlsIXtraResizableControltopic
FlowLayoutPanel with autosize and layout control:
http://www.devexpress.com/Support/Center/p/Q292593.aspx

Вот собственно и все. Исходный код можете скачать здесь

среда, 15 августа 2012 г.

Алфавитное упорядочивание файлов, созданных цифровой камерой Canon

[Alphabetical ordering of files created by digital cameras Canon]

Моя зеркальная камера Canon EOS 600D присваивает, в некоторых случаях, именам файлов префикс “_MG_” вместо ”IMG_”. Данная “проблема” известна также владельцам многих других моделей зеркальных камер от Canon. Это происхоит в том случае, если в установках для Цветового пространства выбрано значение “Adobe RGB” (у меня доступно в творческих режимах). Согласно документации, “Adobe RGB” используется для промышленной обработки фотографии и часто в при распечатке на несовместимых с этим форматом принтерах цвета выглядят несколько приглушенными. При съемке в сюжетных режимах автоматически используется значение “sRGB”.

Так или иначе, если вы пользуетесь обоими режимами (или пользовались ранее и теперь не будете :) ) на выходе получается коллекция фотографий, которую тяжело обрабатывать. Для упорядочивания файлов в порядке их съемки, их нужно соответственно переименовать. Для пользователей линукс есть отличное решение - это программа rename, которая является частью многих дистрибутивов. Чтобы, в конце концов, отличать используемые установки по имени файла, я добавил эту информацию в его конец, выразив ее пост-фиксом “_AdobeRGB”:

rename -v 's/^(_)(MG_.*)\.JPG/I$2_AdobeRGB\.JPG/g' *


Файлы вида “_MG_1234.JPG” переименовываются в “IMG_1234_AdobeRGB.JPG”.

Для предварительного просмотра результатов операции без фактического переименования, можно использовать параметр “-n”. В общем, смотрите документацию к команде, я сам лично обожаю подробные описания wiki на ubuntuusers.de [http://wiki.ubuntuusers.de/rename] на немецком языке.

понедельник, 23 июля 2012 г.

UnityContainer. Передача параметра конструктора в метод Resolve()

[UnityContainer. Pass constructor parameters to Resolve() method]

В одном своем проекте использую UnityContainer - расширяемый DI-контейнер (dependency injection container) от Misrosoft. И понадобилось мне передавать в конструкторы создаваемых контейнером сервисов объекты (DataContext), временем жизни которых я управлял сам. То есть мне понадобилось примерно следующее:

// работаем с одним контекстом данных
DataContext dx1 = container.Resolve();
container.Resolve(/*dx1*/); // конструктор сервиса: public Service1(DataContext dx)
container.Resolve(/*dx1*/); // конструктор сервиса: public Service2(DataContext dx)

// работаем с другим контекстом
DataContext dx2 = container.Resolve();
container.Resolve(/*dx2*/);

Оказалось, что для этой довольно типичной задачи UnityContainer не предлагает однозначного решения. Это обстоятельство порождает множество подходов, которые можно найти в интернете. Одним из таких решений является разрешение (resolve) объекта с переопределением его параметра:

DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve(new ParameterOverride("dx", dx1));

Но здесь нужно быть крайне осторожным, т.к. при изменении имени параметра, контейнер без какого-либо предупреждения инициализирует сервис им вновь созданным объектом DataContext, а наш объект будет попросту проигнорирован.

Многие другие найденные в интернете решения немного модернизированные базировались на указанном подходе. При этом имена параметров извлекались при помощи рефлексии, а реализация заворачивалась в метод расширение (extension).

Другой, приемлемый, на мой взгляд, подход - вынос инициализации сервисов контекстом в отдельный метод:

DataContext dx1 = container.Resolve();
Service1 srv1 = container.Resolve();
Service2 srv2 = container.Resolve();
srv1.Init(dx1);
srv2.Init(dx1);

Я же задействовал потомка контейнера (ChildContainer), что, как мне показалось, является наиболее простым и естественным способом для решения поставленной задачи. Вот что у меня получилось:

DataContext dx1 = container.Resolve();
IUnityContainer childContainer = container.CreateChildContainer();
childContainer.RegisterInstance(dx1, new ExternallyControlledLifetimeManager()); // !!!
Service1 srv1 = childContainer.Resolve();
Service2 srv2 = childContainer.Resolve();
childContainer.Dispose();

Идея проста: В потомке контейнера регистрируем необходимую инстанцию DataContext, создаем с его помощью объект сервиса (Resolve) и в конце заметаем за собой следы (Dispose). Обратите внимание на указание внешнего контроллера времени жизни объекта dx1 (ExternallyControlledLifetimeManager) - тем самым мы исключаем DataContext из утилизации при вызове метода childContainer.Dispose().

Этот код лег в основу метода-расширения, которое я использовал в своем проекте:

public static class UnityExtensions
{
  public static T xResolve(this IUnityContainer container, DataContext dx) {
    using (IUnityContainer childContainer = container.CreateChildContainer())
    {
      childContainer.RegisterInstance(dx, new ExternallyControlledLifetimeManager());
      return childContainer.Resolve();
    }
  }
}

Теперь вызовы выглядят так:

DataContext dx1 = container.Resolve(); // Первая инстанция
Service1 srv1 = container.xResolve(dx1);
Service2 srv2 = container.xResolve(dx1);

DataContext dx3 = container.Resolve(); // Вторая инстанция
Service1 srv3 = container.xResolve(dx3);


PS: К концу написания статьи натолкнулся на еще один способ реализации необходимого функционала - использование переопределения зависимости DependencyOverride:

container.Resolve(new DependencyOverride(dx1));

Здесь объект DataContext встраивается в граф объектов контейнера и используется везде, где есть зависимость этого типа.

понедельник, 2 июля 2012 г.

Исключение файлов из списка копирования xcopy в build событиях Visual Studio (2010)

[Exclude files using xcopy in the build events of Visual Studio (2010)]

Для копирования файлов папки по pre-/post build событиям студии можно использовать команду командной строки - xcopy. Команда имеет множество параметров для самых различных сценариев, полный список которых можно посмотреть, набрав в интерпретаторе cmd “xcopy /?” или, например, здесь (http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/xcopy.mspx?mfr=true). Я, в процессе использования этой команды не имея большого опыта работы с командной строкой и ее инструкциями, столкнулся с рядом не совсем очевидных для меня проблем. Ниже их краткое описание и их решения.

Задача 1: Копирование обновленных файлов одной директории в другую после  построения проекта.

Моя команда копирования в “командной строке события после построения” (согласен, звучит сложно, но из песни слов не выкинешь - это официальное название  командной строки post-build событий в VisualStudio) с использованием макросов выглядела так:

xcopy "$(ProjectDir)SourceFiles" "$(ProjectDir)TargetFiles" /d /e /i

Здесь все выглядит довольно очевидно, но все же краткое описание используемых параметров:
/d - для копирования измененных файлов,
/e - копирование директорий и поддиректорий, даже если они пусты,
/i - будет создана папка назначения, если она не существует.

Замечание: Команда xcopy “чувствительна” к символу “\” в конце имени источника. Не стоит его подставлять, если источником копирования является папка. Команда завершится ошибкой:

xcopy "$(ProjectDir)SourceFiles\" "$(ProjectDir)TargetFiles" /d /e /i REM   (НЕВЕРНО)

Но для копирования всех файлов и под-папок директории можно использовать маску “*.*”. Следующая команда сработает как надо и эквивалентна первому рабочему варианту:

xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i

Задача 2. Исключение определенных файлов из списка копирования

Согласно справке для исключения файлов можно использовать параметр “/exclude” и его синтаксис в справке выглядит довольно просто:

/exclude:filename1[+[filename2]][+[filename3]] : Specifies a list of files containing strings.

Но, к сожалению, использование этой инструкции не так уж тривиально и вызвало у меня ряд затруднений. Например здесь filename1..N не имена исключаемых файлов, а имена файлов, которые содержат описания исключаемых файлов (по одному на каждую строку). Т.е. чтобы исключить файл “a.txt” нужно создать текстовый файл “excludeList.txt” в папке проекта и внести в него строку “a.txt”. Команда копирования файлов приобретает вид:

xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:$(ProjectDir)excludeList.txt

Но эта команда в ряде случаев не может быть выполнена и возвращает ошибку. Эта проблема часто связана с пустыми символами в пути к файлу, которые “разрывают” команду так, что она не может быть правильно интерпретирована. Поэтому мы и обрамили пути источника и цели в кавычки (“), но параметр /exclude не допускает их использования. Чтобы обойти эту проблему можно использовать относительный путь без кавычек, предварительно перейдя в нужную папку при помощи команды cd:

cd "$(ProjectDir)"
"$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:excludeList.txt

Теперь команда будет выполнятся, но исключаемый файл “a.txt” все равно окажется в целевой папке. Немножко помучившись выяснил (= случайно обнаружил), что первая строка файла excludeList.txt игнорируется интерпретатором и для успешного выполнения команды необходимо сместить описания на одну строчку ниже. Файл “excludeList.txt”:

[CR][LF]
a.txt”

На этом можно было бы поставить точку, но я не был удовлетворен тем, что описание исключаемых файлов скрыто от глаз в отельном файле. Мне  захотелось все иметь в одном месте. Для этого файл с описанием исключений можно создавать прямо в скрипте (здесь исключаются файлы “a.txt” и “b.txt”):

cd "$(ProjectDir)"
echo a.txt>EXCLUDE_LIST
echo b.txt>>EXCLUDE_LIST
REM или одной строкой: (echo a.txt&& echo b.txt)>EXCLUDE_LIST
xcopy "$(ProjectDir)SourceFiles\*.*" "$(ProjectDir)TargetFiles" /d /e /i /exclude:EXCLUDE_LIST

Обратите внимание, что обошлось без создания пустой первой строки, скрипт срабатывает на как нужно. Почему? Осталось для меня загадкой, принял как должное.

Замечание. В списке исключаемых файлов не допускается использование символов маски (такие как “*” или “?”), но сами значения, если выражаться “языком шаблонов поиска файлов” как бы обрамляются звездочкой. Например для записи “a.txt” будет происходить исключение файлов соответствующих маске “*a.txt*”.